archive-zip 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HACKING +25 -42
- data/NEWS +25 -0
- data/README +2 -2
- data/Rakefile +202 -0
- data/TODO +5 -0
- data/default.mspec +8 -0
- data/lib/archive/support/binary_stringio.rb +23 -0
- data/lib/archive/support/integer.rb +13 -0
- data/lib/archive/support/io-like.rb +3 -1
- data/lib/archive/support/ioextensions.rb +16 -0
- data/lib/archive/support/iowindow.rb +10 -18
- data/lib/archive/support/time.rb +2 -0
- data/lib/archive/support/zlib.rb +298 -71
- data/lib/archive/zip.rb +161 -139
- data/lib/archive/zip/codec.rb +2 -0
- data/lib/archive/zip/codec/deflate.rb +59 -11
- data/lib/archive/zip/codec/null_encryption.rb +75 -14
- data/lib/archive/zip/codec/store.rb +75 -26
- data/lib/archive/zip/codec/traditional_encryption.rb +146 -35
- data/lib/archive/zip/data_descriptor.rb +6 -4
- data/lib/archive/zip/entry.rb +184 -132
- data/lib/archive/zip/error.rb +2 -0
- data/lib/archive/zip/extra_field.rb +20 -6
- data/lib/archive/zip/extra_field/extended_timestamp.rb +141 -60
- data/lib/archive/zip/extra_field/raw.rb +70 -12
- data/lib/archive/zip/extra_field/unix.rb +58 -16
- data/lib/archive/zip/version.rb +6 -0
- data/spec/archive/zip/codec/deflate/compress/checksum_spec.rb +42 -0
- data/spec/archive/zip/codec/deflate/compress/close_spec.rb +44 -0
- data/spec/archive/zip/codec/deflate/compress/crc32_spec.rb +21 -0
- data/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb +67 -0
- data/spec/archive/zip/codec/deflate/compress/new_spec.rb +37 -0
- data/spec/archive/zip/codec/deflate/compress/open_spec.rb +46 -0
- data/spec/archive/zip/codec/deflate/compress/write_spec.rb +109 -0
- data/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb +18 -0
- data/spec/archive/zip/codec/deflate/decompress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb +18 -0
- data/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb +67 -0
- data/spec/archive/zip/codec/deflate/decompress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/deflate/decompress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/deflate/fixtures/classes.rb +25 -0
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin +1 -0
- data/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/archive/zip/codec/deflate/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb +33 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb +14 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb +27 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb +24 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb +25 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb +57 -0
- data/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb +21 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb +33 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb +14 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb +27 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb +26 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb +50 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb +29 -0
- data/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb +29 -0
- data/spec/archive/zip/codec/null_encryption/fixtures/classes.rb +12 -0
- data/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/store/compress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb +68 -0
- data/spec/archive/zip/codec/store/compress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/store/compress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/store/compress/rewind_spec.rb +26 -0
- data/spec/archive/zip/codec/store/compress/seek_spec.rb +50 -0
- data/spec/archive/zip/codec/store/compress/tell_spec.rb +29 -0
- data/spec/archive/zip/codec/store/compress/write_spec.rb +29 -0
- data/spec/archive/zip/codec/store/decompress/close_spec.rb +33 -0
- data/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb +68 -0
- data/spec/archive/zip/codec/store/decompress/new_spec.rb +14 -0
- data/spec/archive/zip/codec/store/decompress/open_spec.rb +27 -0
- data/spec/archive/zip/codec/store/decompress/read_spec.rb +24 -0
- data/spec/archive/zip/codec/store/decompress/rewind_spec.rb +25 -0
- data/spec/archive/zip/codec/store/decompress/seek_spec.rb +57 -0
- data/spec/archive/zip/codec/store/decompress/tell_spec.rb +21 -0
- data/spec/archive/zip/codec/store/fixtures/classes.rb +12 -0
- data/spec/archive/zip/codec/store/fixtures/raw_file.txt +10 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb +64 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb +18 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb +39 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb +126 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb +38 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb +82 -0
- data/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb +25 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb +64 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb +18 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb +39 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb +41 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb +75 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb +42 -0
- data/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb +127 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb +27 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin +0 -0
- data/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt +10 -0
- data/spec/binary_stringio/new_spec.rb +34 -0
- data/spec/binary_stringio/set_encoding_spec.rb +14 -0
- data/spec/ioextensions/read_exactly_spec.rb +50 -0
- data/spec/zlib/fixtures/classes.rb +65 -0
- data/spec/zlib/fixtures/compressed_file.bin +1 -0
- data/spec/zlib/fixtures/compressed_file_gzip.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_huffman.bin +2 -0
- data/spec/zlib/fixtures/compressed_file_minmem.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_minwin.bin +1 -0
- data/spec/zlib/fixtures/compressed_file_nocomp.bin +0 -0
- data/spec/zlib/fixtures/compressed_file_raw.bin +1 -0
- data/spec/zlib/fixtures/raw_file.txt +10 -0
- data/spec/zlib/zreader/checksum_spec.rb +40 -0
- data/spec/zlib/zreader/close_spec.rb +14 -0
- data/spec/zlib/zreader/compressed_size_spec.rb +18 -0
- data/spec/zlib/zreader/new_spec.rb +41 -0
- data/spec/zlib/zreader/open_spec.rb +49 -0
- data/spec/zlib/zreader/read_spec.rb +47 -0
- data/spec/zlib/zreader/rewind_spec.rb +23 -0
- data/spec/zlib/zreader/seek_spec.rb +55 -0
- data/spec/zlib/zreader/tell_spec.rb +21 -0
- data/spec/zlib/zreader/uncompressed_size_spec.rb +18 -0
- data/spec/zlib/zwriter/checksum_spec.rb +41 -0
- data/spec/zlib/zwriter/close_spec.rb +14 -0
- data/spec/zlib/zwriter/compressed_size_spec.rb +19 -0
- data/spec/zlib/zwriter/new_spec.rb +64 -0
- data/spec/zlib/zwriter/open_spec.rb +68 -0
- data/spec/zlib/zwriter/rewind_spec.rb +26 -0
- data/spec/zlib/zwriter/seek_spec.rb +54 -0
- data/spec/zlib/zwriter/tell_spec.rb +29 -0
- data/spec/zlib/zwriter/uncompressed_size_spec.rb +19 -0
- data/spec/zlib/zwriter/write_spec.rb +28 -0
- data/spec_helper.rb +49 -0
- metadata +296 -74
- data/MANIFEST +0 -27
- data/lib/archive/support/io.rb +0 -14
- data/lib/archive/support/stringio.rb +0 -22
data/lib/archive/zip.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
|
1
|
+
# encoding: UTF-8
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'set'
|
5
5
|
require 'tempfile'
|
6
6
|
|
7
|
-
require 'archive/support/
|
7
|
+
require 'archive/support/ioextensions'
|
8
8
|
require 'archive/support/iowindow'
|
9
|
-
require 'archive/support/stringio'
|
10
9
|
require 'archive/support/time'
|
11
10
|
require 'archive/support/zlib'
|
12
11
|
require 'archive/zip/codec'
|
@@ -44,20 +43,61 @@ module Archive # :nodoc:
|
|
44
43
|
DD_SIGNATURE = "PK\x7\x8" # 0x08074b50
|
45
44
|
|
46
45
|
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
|
52
|
-
|
46
|
+
# Creates or possibly updates an archive using _paths_ for new contents.
|
47
|
+
#
|
48
|
+
# If _archive_ is a String, it is treated as a file path which will receive
|
49
|
+
# the archive contents. If the file already exists, it is assumed to be an
|
50
|
+
# archive and will be updated "in place". Otherwise, a new archive
|
51
|
+
# is created. The archive will be closed once written.
|
52
|
+
#
|
53
|
+
# If _archive_ has any other kind of value, it is treated as a writable
|
54
|
+
# IO-like object which will be left open after the completion of this
|
55
|
+
# method.
|
56
|
+
#
|
57
|
+
# <b>NOTE:</b> No attempt is made to prevent adding multiple entries with
|
58
|
+
# the same archive path.
|
59
|
+
#
|
60
|
+
# See the instance method #archive for more information about _paths_ and
|
61
|
+
# _options_.
|
62
|
+
def self.archive(archive, paths, options = {})
|
63
|
+
if archive.kind_of?(String) && File.exist?(archive) then
|
64
|
+
# Update the archive "in place".
|
65
|
+
tmp_archive_path = nil
|
66
|
+
File.open(archive) do |archive_in|
|
67
|
+
Tempfile.open(*File.split(archive_in.path).reverse) do |archive_out|
|
68
|
+
# Save off the path so that the temporary file can be renamed to the
|
69
|
+
# archive file later.
|
70
|
+
tmp_archive_path = archive_out.path
|
71
|
+
# Ensure the file is in binary mode for Windows.
|
72
|
+
archive_out.binmode
|
73
|
+
# Update the archive.
|
74
|
+
open(archive_in, :r) do |z_in|
|
75
|
+
open(archive_out, :w) do |z_out|
|
76
|
+
z_in.each { |entry| z_out << entry }
|
77
|
+
z_out.archive(paths, options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# Set more reasonable permissions than those set by Tempfile.
|
83
|
+
File.chmod(0666 & ~File.umask, tmp_archive_path)
|
84
|
+
# Replace the input archive with the output archive.
|
85
|
+
File.rename(tmp_archive_path, archive)
|
86
|
+
else
|
87
|
+
open(archive, :w) { |z| z.archive(paths, options) }
|
88
|
+
end
|
53
89
|
end
|
54
90
|
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
|
60
|
-
|
91
|
+
# Extracts the entries from an archive to _destination_.
|
92
|
+
#
|
93
|
+
# If _archive_ is a String, it is treated as a file path pointing to an
|
94
|
+
# existing archive file. Otherwise, it is treated as a seekable and
|
95
|
+
# readable IO-like object.
|
96
|
+
#
|
97
|
+
# See the instance method #extract for more information about _destination_
|
98
|
+
# and _options_.
|
99
|
+
def self.extract(archive, destination, options = {})
|
100
|
+
open(archive, :r) { |z| z.extract(destination, options) }
|
61
101
|
end
|
62
102
|
|
63
103
|
# Calls #new with the given arguments and yields the resulting Zip instance
|
@@ -65,8 +105,8 @@ module Archive # :nodoc:
|
|
65
105
|
# Zip instance is closed.
|
66
106
|
#
|
67
107
|
# This is a synonym for #new if no block is given.
|
68
|
-
def self.open(
|
69
|
-
zf = new(
|
108
|
+
def self.open(archive, mode = :r)
|
109
|
+
zf = new(archive, mode)
|
70
110
|
return zf unless block_given?
|
71
111
|
|
72
112
|
begin
|
@@ -76,140 +116,121 @@ module Archive # :nodoc:
|
|
76
116
|
end
|
77
117
|
end
|
78
118
|
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
119
|
+
# Opens an existing archive and/or creates a new archive.
|
120
|
+
#
|
121
|
+
# If _archive_ is a String, it will be treated as a file path; otherwise, it
|
122
|
+
# is assumed to be an IO-like object with the necessary read or write
|
123
|
+
# support depending on the setting of _mode_. IO-like objects are not
|
124
|
+
# closed when the archive is closed, but files opened from file paths are.
|
125
|
+
# Set _mode_ to <tt>:r</tt> or <tt>"r"</tt> to read the archive, and set it
|
126
|
+
# to <tt>:w</tt> or <tt>"w"</tt> to write the archive.
|
127
|
+
#
|
128
|
+
# <b>NOTE:</b> The #close method must be called in order to save any
|
129
|
+
# modifications to the archive. Due to limitations in the Ruby finalization
|
130
|
+
# capabilities, the #close method is _not_ automatically called when this
|
131
|
+
# object is garbage collected. Make sure to call #close when finished with
|
132
|
+
# this object.
|
133
|
+
def initialize(archive, mode = :r)
|
134
|
+
@archive = archive
|
135
|
+
mode = mode.to_sym
|
136
|
+
if mode == :r || mode == :w then
|
137
|
+
@mode = mode
|
138
|
+
else
|
139
|
+
raise ArgumentError, "illegal access mode #{mode}"
|
140
|
+
end
|
141
|
+
|
142
|
+
@close_delegate = false
|
143
|
+
if @archive.kind_of?(String) then
|
144
|
+
@close_delegate = true
|
145
|
+
if mode == :r then
|
146
|
+
@archive = File.open(@archive, 'rb')
|
147
|
+
else
|
148
|
+
@archive = File.open(@archive, 'wb')
|
149
|
+
end
|
95
150
|
end
|
96
|
-
@
|
97
|
-
@archive_out = archive_out
|
98
|
-
@entries = {}
|
99
|
-
@dirty = false
|
151
|
+
@entries = []
|
100
152
|
@comment = ''
|
101
153
|
@closed = false
|
102
|
-
if ! @archive_path.nil? && File.exist?(@archive_path) then
|
103
|
-
@archive_in = File.new(@archive_path, 'rb')
|
104
|
-
parse(@archive_in)
|
105
|
-
end
|
106
154
|
end
|
107
155
|
|
108
156
|
# A comment string for the ZIP archive.
|
109
157
|
attr_accessor :comment
|
110
158
|
|
111
|
-
#
|
112
|
-
#
|
159
|
+
# Closes the archive.
|
160
|
+
#
|
161
|
+
# Failure to close the archive by calling this method may result in a loss
|
162
|
+
# of data for writable archives.
|
163
|
+
#
|
164
|
+
# <b>NOTE:</b> The underlying stream is only closed if the archive was
|
165
|
+
# opened with a String for the _archive_ parameter.
|
113
166
|
#
|
114
167
|
# Raises Archive::Zip::IOError if called more than once.
|
115
168
|
def close
|
116
169
|
raise IOError, 'closed archive' if closed?
|
117
170
|
|
118
|
-
if
|
119
|
-
#
|
120
|
-
|
121
|
-
# Update the archive "in place".
|
122
|
-
tmp_archive_path = nil
|
123
|
-
Tempfile.open(*File.split(@archive_path).reverse) do |archive_out|
|
124
|
-
# Ensure the file is in binary mode for Windows.
|
125
|
-
archive_out.binmode
|
126
|
-
# Save off the path so that the temporary file can be renamed to the
|
127
|
-
# archive file later.
|
128
|
-
tmp_archive_path = archive_out.path
|
129
|
-
dump(archive_out)
|
130
|
-
end
|
131
|
-
File.chmod(0666 & ~File.umask, tmp_archive_path)
|
132
|
-
elsif @archive_out.kind_of?(String) then
|
133
|
-
# Open a new archive to receive the data.
|
134
|
-
File.open(@archive_out, 'wb') do |archive_out|
|
135
|
-
dump(archive_out)
|
136
|
-
end
|
137
|
-
else
|
138
|
-
# Assume the given object is an IO-like object and dump the archive
|
139
|
-
# contents to it.
|
140
|
-
dump(@archive_out)
|
141
|
-
end
|
142
|
-
@archive_in.close unless @archive_in.nil?
|
143
|
-
# The rename must happen after the original archive is closed when
|
144
|
-
# running on Windows since that platform does not allow a file which is
|
145
|
-
# in use to be replaced as is required when trying to update the archive
|
146
|
-
# "in place".
|
147
|
-
File.rename(tmp_archive_path, @archive_path) if @archive_out.nil?
|
148
|
-
elsif ! @archive_in.nil? then
|
149
|
-
@archive_in.close
|
171
|
+
if writable? then
|
172
|
+
# Write the new archive contents.
|
173
|
+
dump(@archive)
|
150
174
|
end
|
151
175
|
|
152
|
-
|
176
|
+
# Note that we only close delegate streams which are opened by us so that
|
177
|
+
# the user may do so for other delegate streams at his/her discretion.
|
178
|
+
@archive.close if @close_delegate
|
179
|
+
|
180
|
+
@closed = true
|
153
181
|
nil
|
154
182
|
end
|
155
183
|
|
156
|
-
# Returns +true+ if the ZIP archive is closed, false otherwise.
|
184
|
+
# Returns +true+ if the ZIP archive is closed, +false+ otherwise.
|
157
185
|
def closed?
|
158
186
|
@closed
|
159
187
|
end
|
160
188
|
|
161
|
-
#
|
162
|
-
|
163
|
-
|
189
|
+
# Returns +true+ if the ZIP archive is readable, +false+ otherwise.
|
190
|
+
def readable?
|
191
|
+
@mode == :r
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns +true+ if the ZIP archive is writable, +false+ otherwise.
|
195
|
+
def writable?
|
196
|
+
@mode == :w
|
197
|
+
end
|
198
|
+
|
199
|
+
# Iterates through each entry of a readable ZIP archive in turn yielding
|
200
|
+
# each one to the given block.
|
164
201
|
#
|
165
|
-
# Raises Archive::Zip::IOError if called after
|
202
|
+
# Raises Archive::Zip::IOError if called on a non-readable archive or after
|
203
|
+
# the archive is closed.
|
166
204
|
def each(&b)
|
167
|
-
raise IOError, '
|
205
|
+
raise IOError, 'non-readable archive' unless readable?
|
206
|
+
raise IOError, 'closed archive' if closed?
|
168
207
|
|
169
|
-
@
|
208
|
+
unless @parse_complete then
|
209
|
+
parse(@archive)
|
210
|
+
@parse_complete = true
|
211
|
+
end
|
212
|
+
@entries.each(&b)
|
170
213
|
end
|
171
214
|
|
172
|
-
#
|
173
|
-
#
|
215
|
+
# Adds _entry_ into a writable ZIP archive.
|
216
|
+
#
|
217
|
+
# <b>NOTE:</b> No attempt is made to prevent adding multiple entries with
|
218
|
+
# the same archive path.
|
174
219
|
#
|
175
|
-
# Raises Archive::Zip::IOError if called after
|
220
|
+
# Raises Archive::Zip::IOError if called on a non-writable archive or after
|
221
|
+
# the archive is closed.
|
176
222
|
def add_entry(entry)
|
177
|
-
raise IOError, '
|
223
|
+
raise IOError, 'non-writable archive' unless writable?
|
224
|
+
raise IOError, 'closed archive' if closed?
|
178
225
|
unless entry.kind_of?(Entry) then
|
179
226
|
raise ArgumentError, 'Archive::Zip::Entry instance required'
|
180
227
|
end
|
181
228
|
|
182
|
-
@entries
|
183
|
-
@dirty = true
|
229
|
+
@entries << entry
|
184
230
|
self
|
185
231
|
end
|
186
232
|
alias :<< :add_entry
|
187
233
|
|
188
|
-
# Look up an entry based on the zip path located in _zip_path_. Returns
|
189
|
-
# +nil+ if no entry is found.
|
190
|
-
def get_entry(zip_path)
|
191
|
-
@entries[zip_path]
|
192
|
-
end
|
193
|
-
alias :[] :get_entry
|
194
|
-
|
195
|
-
# Removes an entry from the ZIP file and returns the entry or +nil+ if no
|
196
|
-
# entry was found to remove. If _entry_ is an instance of
|
197
|
-
# Archive::Zip::Entry, the zip_path attribute is used to find the entry to
|
198
|
-
# remove; otherwise, _entry_ is assumed to be a zip path matching an entry
|
199
|
-
# in the ZIP archive.
|
200
|
-
#
|
201
|
-
# Raises Archive::Zip::IOError if called after #close.
|
202
|
-
def remove_entry(entry)
|
203
|
-
raise IOError, 'closed archive' if @closed
|
204
|
-
|
205
|
-
zip_path = entry
|
206
|
-
zip_path = entry.zip_path if entry.kind_of?(Entry)
|
207
|
-
entry = @entries.delete(zip_path)
|
208
|
-
entry = entry[1] unless entry.nil?
|
209
|
-
@dirty ||= ! entry.nil?
|
210
|
-
entry
|
211
|
-
end
|
212
|
-
|
213
234
|
# Adds _paths_ to the archive. _paths_ may be either a single path or an
|
214
235
|
# Array of paths. The files and directories referenced by _paths_ are added
|
215
236
|
# using their respective basenames as their zip paths. The exception to
|
@@ -267,10 +288,13 @@ module Archive # :nodoc:
|
|
267
288
|
# Any other options which are supported by Archive::Zip::Entry.from_file are
|
268
289
|
# also supported.
|
269
290
|
#
|
270
|
-
#
|
271
|
-
#
|
272
|
-
#
|
273
|
-
# Archive::Zip::
|
291
|
+
# <b>NOTE:</b> No attempt is made to prevent adding multiple entries with
|
292
|
+
# the same archive path.
|
293
|
+
#
|
294
|
+
# Raises Archive::Zip::IOError if called on a non-writable archive or after
|
295
|
+
# the archive is closed. Raises Archive::Zip::EntryError if the
|
296
|
+
# <b>:on_error</b> option is either unset or indicates that the error should
|
297
|
+
# be raised and Archive::Zip::Entry.from_file raises an error.
|
274
298
|
#
|
275
299
|
# == Example
|
276
300
|
#
|
@@ -317,7 +341,8 @@ module Archive # :nodoc:
|
|
317
341
|
# zip-test/dir1/
|
318
342
|
# zip-test/dir2/
|
319
343
|
def archive(paths, options = {})
|
320
|
-
raise IOError, '
|
344
|
+
raise IOError, 'non-writable archive' unless writable?
|
345
|
+
raise IOError, 'closed archive' if closed?
|
321
346
|
|
322
347
|
# Ensure that paths is an enumerable.
|
323
348
|
paths = [paths] unless paths.kind_of?(Enumerable)
|
@@ -472,7 +497,8 @@ module Archive # :nodoc:
|
|
472
497
|
# Any other options which are supported by Archive::Zip::Entry#extract are
|
473
498
|
# also supported.
|
474
499
|
#
|
475
|
-
# Raises Archive::Zip::IOError if called after
|
500
|
+
# Raises Archive::Zip::IOError if called on a non-readable archive or after
|
501
|
+
# the archive is closed.
|
476
502
|
#
|
477
503
|
# == Example
|
478
504
|
#
|
@@ -529,7 +555,8 @@ module Archive # :nodoc:
|
|
529
555
|
# +- dir1
|
530
556
|
# +- dir2
|
531
557
|
def extract(destination, options = {})
|
532
|
-
raise IOError, '
|
558
|
+
raise IOError, 'non-readable archive' unless readable?
|
559
|
+
raise IOError, 'closed archive' if closed?
|
533
560
|
|
534
561
|
# Ensure that unspecified options have default values.
|
535
562
|
options[:directories] = true unless options.has_key?(:directories)
|
@@ -627,23 +654,16 @@ module Archive # :nodoc:
|
|
627
654
|
|
628
655
|
private
|
629
656
|
|
630
|
-
# <b>NOTE:</b> For now _io_ MUST be seekable
|
631
|
-
# +true+ from its seekable? method. See IO#seekable?.
|
632
|
-
#
|
633
|
-
# Raises Archive::Zip::IOError if _io_ is not seekable.
|
657
|
+
# <b>NOTE:</b> For now _io_ MUST be seekable.
|
634
658
|
def parse(io)
|
635
|
-
# Error out if the IO object is not confirmed seekable.
|
636
|
-
raise Zip::IOError, 'non-seekable IO object given' unless io.respond_to?(:seekable?) and io.seekable?
|
637
|
-
|
638
659
|
socd_pos = find_central_directory(io)
|
639
660
|
io.seek(socd_pos)
|
640
661
|
# Parse each entry in the central directory.
|
641
662
|
loop do
|
642
|
-
signature =
|
663
|
+
signature = IOExtensions.read_exactly(io, 4)
|
643
664
|
break unless signature == CFH_SIGNATURE
|
644
|
-
|
665
|
+
@entries << Zip::Entry.parse(io)
|
645
666
|
end
|
646
|
-
@dirty = false
|
647
667
|
# Maybe add support for digital signatures and ZIP64 records... Later
|
648
668
|
|
649
669
|
nil
|
@@ -665,9 +685,12 @@ module Archive # :nodoc:
|
|
665
685
|
eocd_offset = -22
|
666
686
|
loop do
|
667
687
|
io.seek(eocd_offset, IO::SEEK_END)
|
668
|
-
if
|
688
|
+
if IOExtensions.read_exactly(io, 4) == EOCD_SIGNATURE then
|
669
689
|
io.seek(16, IO::SEEK_CUR)
|
670
|
-
|
690
|
+
if IOExtensions.read_exactly(io, 2).unpack('v')[0] ==
|
691
|
+
(eocd_offset + 22).abs then
|
692
|
+
break
|
693
|
+
end
|
671
694
|
end
|
672
695
|
eocd_offset -= 1
|
673
696
|
end
|
@@ -676,7 +699,7 @@ module Archive # :nodoc:
|
|
676
699
|
# Now, jump into the location in the record which contains a pointer to
|
677
700
|
# the start of the central directory record and return the value.
|
678
701
|
io.seek(eocd_offset + 16, IO::SEEK_END)
|
679
|
-
return
|
702
|
+
return IOExtensions.read_exactly(io, 4).unpack('V')[0]
|
680
703
|
rescue Errno::EINVAL
|
681
704
|
raise Zip::UnzipError, 'unable to locate end-of-central-directory record'
|
682
705
|
end
|
@@ -686,12 +709,11 @@ module Archive # :nodoc:
|
|
686
709
|
# bytes written.
|
687
710
|
def dump(io)
|
688
711
|
bytes_written = 0
|
689
|
-
entries
|
690
|
-
entries.each do |entry|
|
712
|
+
@entries.each do |entry|
|
691
713
|
bytes_written += entry.dump_local_file_record(io, bytes_written)
|
692
714
|
end
|
693
715
|
central_directory_offset = bytes_written
|
694
|
-
entries.each do |entry|
|
716
|
+
@entries.each do |entry|
|
695
717
|
bytes_written += entry.dump_central_file_record(io)
|
696
718
|
end
|
697
719
|
central_directory_length = bytes_written - central_directory_offset
|
@@ -700,8 +722,8 @@ module Archive # :nodoc:
|
|
700
722
|
[
|
701
723
|
0,
|
702
724
|
0,
|
703
|
-
entries.length,
|
704
|
-
entries.length,
|
725
|
+
@entries.length,
|
726
|
+
@entries.length,
|
705
727
|
central_directory_length,
|
706
728
|
central_directory_offset,
|
707
729
|
comment.length
|
data/lib/archive/zip/codec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'archive/support/zlib'
|
2
4
|
require 'archive/zip/codec'
|
3
5
|
require 'archive/zip/data_descriptor'
|
@@ -40,8 +42,17 @@ module Archive; class Zip; module Codec
|
|
40
42
|
# compression to be applied to the data stream.
|
41
43
|
def initialize(io, compression_level)
|
42
44
|
super(io, compression_level, -Zlib::MAX_WBITS)
|
45
|
+
@crc32 = 0
|
43
46
|
end
|
44
47
|
|
48
|
+
# The CRC32 checksum of the uncompressed data written using this object.
|
49
|
+
#
|
50
|
+
# <b>NOTE:</b> Anything still in the internal write buffer has not been
|
51
|
+
# processed, so calling #flush prior to examining this attribute may be
|
52
|
+
# necessary for an accurate computation.
|
53
|
+
attr_reader :crc32
|
54
|
+
alias :checksum :crc32
|
55
|
+
|
45
56
|
# Closes this object so that further write operations will fail. If
|
46
57
|
# _close_delegate_ is +true+, the delegate object used as a data sink will
|
47
58
|
# also be closed using its close method.
|
@@ -50,15 +61,29 @@ module Archive; class Zip; module Codec
|
|
50
61
|
delegate.close if close_delegate
|
51
62
|
end
|
52
63
|
|
53
|
-
# Returns an instance of Archive::Zip::
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
64
|
+
# Returns an instance of Archive::Zip::DataDescriptor with information
|
65
|
+
# regarding the data which has passed through this object to the delegate
|
66
|
+
# object. The close or flush methods should be called before using this
|
67
|
+
# method in order to ensure that any possibly buffered data is flushed to
|
68
|
+
# the delegate object; otherwise, the contents of the data descriptor may
|
69
|
+
# be inaccurate.
|
59
70
|
def data_descriptor
|
60
71
|
DataDescriptor.new(crc32, compressed_size, uncompressed_size)
|
61
72
|
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
77
|
+
result = super(offset, whence)
|
78
|
+
@crc32 = 0 if whence == IO::SEEK_SET
|
79
|
+
result
|
80
|
+
end
|
81
|
+
|
82
|
+
def unbuffered_write(string)
|
83
|
+
result = super(string)
|
84
|
+
@crc32 = Zlib.crc32(string, @crc32)
|
85
|
+
result
|
86
|
+
end
|
62
87
|
end
|
63
88
|
|
64
89
|
# Archive::Zip::Codec::Deflate::Decompress extends Zlib::ZReader in order to
|
@@ -92,8 +117,17 @@ module Archive; class Zip; module Codec
|
|
92
117
|
# method, this class' _rewind_ method will be enabled.
|
93
118
|
def initialize(io)
|
94
119
|
super(io, -Zlib::MAX_WBITS)
|
120
|
+
@crc32 = 0
|
95
121
|
end
|
96
122
|
|
123
|
+
# The CRC32 checksum of the uncompressed data read using this object.
|
124
|
+
#
|
125
|
+
# <b>NOTE:</b> The contents of the internal read buffer are immediately
|
126
|
+
# processed any time the internal buffer is filled, so this checksum is
|
127
|
+
# only accurate if all data has been read out of this object.
|
128
|
+
attr_reader :crc32
|
129
|
+
alias :checksum :crc32
|
130
|
+
|
97
131
|
# Closes this object so that further read operations will fail. If
|
98
132
|
# _close_delegate_ is +true+, the delegate object used as a data source
|
99
133
|
# will also be closed using its close method.
|
@@ -102,14 +136,28 @@ module Archive; class Zip; module Codec
|
|
102
136
|
delegate.close if close_delegate
|
103
137
|
end
|
104
138
|
|
105
|
-
# Returns an instance of Archive::Zip::
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
139
|
+
# Returns an instance of Archive::Zip::DataDescriptor with information
|
140
|
+
# regarding the data which has passed through this object from the
|
141
|
+
# delegate object. It is recommended to call the close method before
|
142
|
+
# calling this in order to ensure that no further read operations change
|
143
|
+
# the state of this object.
|
110
144
|
def data_descriptor
|
111
145
|
DataDescriptor.new(crc32, compressed_size, uncompressed_size)
|
112
146
|
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def unbuffered_read(length)
|
151
|
+
result = super(length)
|
152
|
+
@crc32 = Zlib.crc32(result, @crc32)
|
153
|
+
result
|
154
|
+
end
|
155
|
+
|
156
|
+
def unbuffered_seek(offset, whence = IO::SEEK_SET)
|
157
|
+
result = super(offset, whence)
|
158
|
+
@crc32 = 0 if whence == IO::SEEK_SET
|
159
|
+
result
|
160
|
+
end
|
113
161
|
end
|
114
162
|
|
115
163
|
# The numeric identifier assigned to this compression codec by the ZIP
|