pure_ruby_zip 0.1.4
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 +7 -0
- data/bin/pure-ruby-zip +7 -0
- data/lib/pure_ruby_zip.rb +276 -0
- data/lib/pure_ruby_zip/version.rb +3 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d1ea1789a09e61e0950554765cc23a4d01274533fe2a4faefc0ad17295e3bcf5
|
|
4
|
+
data.tar.gz: 7f3ad14e5e7a24f64dc8b3742644aecce8eaf9cdef250a40b7eec50d4ca3a05e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 842d6239f37fcde66242e1e8ef1e8f275dce4f6c16d12127199956d1fe13d7feadea14bbeb2133e2e60d784d183da88517f4f9b1454c34185675fbc822287f17
|
|
7
|
+
data.tar.gz: 2c16043e638b6d89624ed52f04d871e89e7f7366db52b55471b1a3a228f9bc2581e7ea93f12110e02853d25413f6568f4a2a3943e3fc0587d61a6dcc809eb35f
|
data/bin/pure-ruby-zip
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
require "pure_ruby_zip/version.rb"
|
|
2
|
+
|
|
3
|
+
module PureRubyZip
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
# Your code goes here...
|
|
6
|
+
CODE_LENGTH_CODES_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]
|
|
7
|
+
LENGTH_EXTRA_BITS = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0]
|
|
8
|
+
LENGTH_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163,
|
|
9
|
+
195, 227, 258]
|
|
10
|
+
DISTANCE_EXTRA_BITS = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13,
|
|
11
|
+
13]
|
|
12
|
+
DISTANCE_BASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049,
|
|
13
|
+
3073, 4097, 6145, 8193, 12289, 16385, 24577]
|
|
14
|
+
class Bitstream
|
|
15
|
+
attr_accessor :byte
|
|
16
|
+
attr_accessor :bit_index
|
|
17
|
+
attr_accessor :file
|
|
18
|
+
def initialize(data)
|
|
19
|
+
@data = data
|
|
20
|
+
@bit_index = 0
|
|
21
|
+
end
|
|
22
|
+
def read_bit
|
|
23
|
+
res = ((@data[0].codepoints.first >> @bit_index) & 1) == 1
|
|
24
|
+
if @bit_index == 7
|
|
25
|
+
@data = @data[1..-1]
|
|
26
|
+
@bit_index = 0
|
|
27
|
+
else
|
|
28
|
+
@bit_index += 1
|
|
29
|
+
end
|
|
30
|
+
res
|
|
31
|
+
end
|
|
32
|
+
def read_int(n_bits)
|
|
33
|
+
res = 0
|
|
34
|
+
(0..(n_bits - 1)).each { |i| res += (read_bit ? 1 : 0) << i }
|
|
35
|
+
res
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
class ZipDecompressor
|
|
39
|
+
def decode_symbol(tree, file_bitstream)
|
|
40
|
+
bits = []
|
|
41
|
+
while true
|
|
42
|
+
bit = file_bitstream.read_bit
|
|
43
|
+
bits += [bit]
|
|
44
|
+
key = bits.map { |x| x ? "1" : "0" }.join("")
|
|
45
|
+
return tree[key] if tree[key]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
def inflate_block_data(litlen_tree, dist_tree, file_data, file_bitstream)
|
|
49
|
+
data = file_data
|
|
50
|
+
loop do
|
|
51
|
+
sym = decode_symbol(litlen_tree, file_bitstream)
|
|
52
|
+
if sym < 256
|
|
53
|
+
data += sym.chr
|
|
54
|
+
elsif sym == 256
|
|
55
|
+
return data
|
|
56
|
+
else
|
|
57
|
+
sym -= 257
|
|
58
|
+
length = file_bitstream.read_int(LENGTH_EXTRA_BITS[sym]) + LENGTH_BASE[sym]
|
|
59
|
+
dist_sym = decode_symbol(dist_tree, file_bitstream)
|
|
60
|
+
dist = file_bitstream.read_int(DISTANCE_EXTRA_BITS[dist_sym]) + DISTANCE_BASE[dist_sym]
|
|
61
|
+
reference_data = []
|
|
62
|
+
(0..(length - 1)).each {
|
|
63
|
+
char = data[-dist]
|
|
64
|
+
data += char
|
|
65
|
+
reference_data += [char]
|
|
66
|
+
}
|
|
67
|
+
x = reference_data[0..10].join("").codepoints.join(" ")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
data
|
|
71
|
+
end
|
|
72
|
+
def bit_lengths_to_tree(bit_lengths)
|
|
73
|
+
max_bits = bit_lengths.max
|
|
74
|
+
bitlen_counts = (0..max_bits).map { |count| bit_lengths.count { |length| length == count && length != 0 } }
|
|
75
|
+
next_code = [0, 0]
|
|
76
|
+
(2..max_bits).each do |i|
|
|
77
|
+
next_code[i] = ((next_code[i - 1] || 0) + bitlen_counts[i - 1]) << 1
|
|
78
|
+
end
|
|
79
|
+
tree = {}
|
|
80
|
+
bit_lengths.each.with_index do |length, index|
|
|
81
|
+
if length != 0
|
|
82
|
+
tree[next_code[length].to_s(2).rjust(length, "0")] = index
|
|
83
|
+
next_code[length] += 1
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
tree
|
|
87
|
+
end
|
|
88
|
+
def decode_uncompressed_block(file_data, file_bitstream)
|
|
89
|
+
file_bitstream.read_int(5)
|
|
90
|
+
length = file_bitstream.read_int(16)
|
|
91
|
+
file_bitstream.read_int(16)
|
|
92
|
+
(0..(length - 1)).map { |x| file_bitstream.read_int(8).chr }.join("")
|
|
93
|
+
end
|
|
94
|
+
def decode_fixed_huffman_compressed_block(file_data, file_bitstream)
|
|
95
|
+
litlen_bit_lengths = [8] * 144 + [9] * (256 - 144) + [7] * (280 - 256) + [8] * (286 - 280)
|
|
96
|
+
litlen_tree = bit_lengths_to_tree(litlen_bit_lengths)
|
|
97
|
+
dist_bit_lengths = [5] * 30
|
|
98
|
+
dist_tree = bit_lengths_to_tree(dist_bit_lengths)
|
|
99
|
+
inflate_block_data(litlen_tree, dist_tree, file_data, file_bitstream)
|
|
100
|
+
end
|
|
101
|
+
def decode_dynamic_huffman_compressed_block(file_data, file_bitstream)
|
|
102
|
+
hlit = file_bitstream.read_int(5) + 257
|
|
103
|
+
hdist = file_bitstream.read_int(5) + 1
|
|
104
|
+
hclen = file_bitstream.read_int(4) + 4
|
|
105
|
+
code_length_bit_lengths = [0] * 19
|
|
106
|
+
(0..(hclen - 1)).each { |len| code_length_bit_lengths[CODE_LENGTH_CODES_ORDER[len]] = file_bitstream.read_int(3) }
|
|
107
|
+
code_length_tree = bit_lengths_to_tree(code_length_bit_lengths)
|
|
108
|
+
bit_lengths = []
|
|
109
|
+
while bit_lengths.count < hlit + hdist
|
|
110
|
+
sym = decode_symbol(code_length_tree, file_bitstream)
|
|
111
|
+
if sym < 16
|
|
112
|
+
bit_lengths += [sym]
|
|
113
|
+
elsif sym == 16
|
|
114
|
+
prev_byte = bit_lengths[-1]
|
|
115
|
+
bit_lengths += [prev_byte] * (file_bitstream.read_int(2) + 3)
|
|
116
|
+
elsif sym == 17
|
|
117
|
+
bit_lengths += [0] * (file_bitstream.read_int(3) + 3)
|
|
118
|
+
elsif sym == 18
|
|
119
|
+
bit_lengths += [0] * (file_bitstream.read_int(7) + 11)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
litlen_tree = bit_lengths_to_tree(bit_lengths[0..(hlit - 1)])
|
|
123
|
+
dist_tree = bit_lengths_to_tree(bit_lengths[hlit..-1])
|
|
124
|
+
inflate_block_data(litlen_tree, dist_tree, file_data, file_bitstream)
|
|
125
|
+
end
|
|
126
|
+
def decode_zipped_file(file_bitstream)
|
|
127
|
+
file_data = ""
|
|
128
|
+
is_last_block = false
|
|
129
|
+
until is_last_block
|
|
130
|
+
is_last_block = file_bitstream.read_bit
|
|
131
|
+
block_type = file_bitstream.read_int(2)
|
|
132
|
+
file_data += if block_type == 0
|
|
133
|
+
decode_uncompressed_block(file_data, file_bitstream)
|
|
134
|
+
elsif block_type == 1
|
|
135
|
+
decode_fixed_huffman_compressed_block(file_data, file_bitstream)
|
|
136
|
+
else
|
|
137
|
+
decode_dynamic_huffman_compressed_block(file_data, file_bitstream)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
file_data
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
module ZipHelpers
|
|
144
|
+
def read_int(file, bytes)
|
|
145
|
+
file.read(bytes).codepoints.each.with_index.reduce(0) { |acc, x| acc + x[0] * (256 ** x[1]) }
|
|
146
|
+
end
|
|
147
|
+
def find_string(file, string)
|
|
148
|
+
search_fifo = ""
|
|
149
|
+
while search_fifo != string
|
|
150
|
+
search_fifo = (search_fifo.length == 4 ? search_fifo[1..-1] : search_fifo) + file.read(1)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
def skip(file, bytes)
|
|
154
|
+
file.read bytes
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
class ZipFileItem
|
|
158
|
+
include ZipHelpers
|
|
159
|
+
def initialize(filename, offset)
|
|
160
|
+
@filename = filename
|
|
161
|
+
@offset = offset
|
|
162
|
+
end
|
|
163
|
+
def read_data(zipfile)
|
|
164
|
+
# Skip to compressed length
|
|
165
|
+
skip zipfile, 8
|
|
166
|
+
# Get compressed length
|
|
167
|
+
compressed_size = read_int zipfile, 4
|
|
168
|
+
# Skip to extra data length
|
|
169
|
+
skip zipfile, 6
|
|
170
|
+
# Get extra data length
|
|
171
|
+
extra_data_length = read_int zipfile, 2
|
|
172
|
+
# Skip to file data
|
|
173
|
+
skip zipfile, @filename.length + extra_data_length
|
|
174
|
+
# Read file data
|
|
175
|
+
zipfile.read compressed_size
|
|
176
|
+
end
|
|
177
|
+
def handle_compressed(zipfile)
|
|
178
|
+
data = read_data zipfile
|
|
179
|
+
b = Bitstream.new data
|
|
180
|
+
z = ZipDecompressor.new
|
|
181
|
+
z.decode_zipped_file b
|
|
182
|
+
end
|
|
183
|
+
def handle_uncompressed(zipfile)
|
|
184
|
+
read_data zipfile
|
|
185
|
+
end
|
|
186
|
+
def get_decompressed_data(zipfile)
|
|
187
|
+
zipfile.seek @offset
|
|
188
|
+
# Skip to compression type
|
|
189
|
+
skip zipfile, 8
|
|
190
|
+
# Read compression type
|
|
191
|
+
compression_type = read_int zipfile, 2
|
|
192
|
+
data = if compression_type == 8
|
|
193
|
+
handle_compressed zipfile
|
|
194
|
+
else
|
|
195
|
+
handle_uncompressed zipfile
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
class ZipFile
|
|
200
|
+
include ZipHelpers
|
|
201
|
+
private
|
|
202
|
+
def find_eocd(file)
|
|
203
|
+
find_string file, "\x50\x4b\x05\x06"
|
|
204
|
+
end
|
|
205
|
+
def find_central_directory(file)
|
|
206
|
+
find_string file, "\x50\x4b\x01\x02"
|
|
207
|
+
end
|
|
208
|
+
def get_number_of_items(file)
|
|
209
|
+
# Find the EOCD (End Of Central Directory)
|
|
210
|
+
find_eocd file
|
|
211
|
+
# Skip to number of entries (items in zipfile)
|
|
212
|
+
skip file, 6
|
|
213
|
+
# Read number of entries
|
|
214
|
+
num_entries = read_int file, 2
|
|
215
|
+
end
|
|
216
|
+
def get_items_metadata(file)
|
|
217
|
+
File.open(@filename) do |file|
|
|
218
|
+
# Read number of entries
|
|
219
|
+
num_entries = get_number_of_items file
|
|
220
|
+
# Rewind file
|
|
221
|
+
file.seek 0
|
|
222
|
+
@items = Hash[(0..(num_entries - 1)).map {
|
|
223
|
+
# Find central directory record
|
|
224
|
+
find_central_directory file
|
|
225
|
+
# Skip to filename length
|
|
226
|
+
skip file, 24
|
|
227
|
+
# Get filename length
|
|
228
|
+
filename_length = read_int file, 2
|
|
229
|
+
# Skip to file offset
|
|
230
|
+
skip file, 12
|
|
231
|
+
# Get file offset
|
|
232
|
+
file_offset = read_int file, 4
|
|
233
|
+
# Get filename
|
|
234
|
+
filename = file.read(filename_length)
|
|
235
|
+
[filename, ZipFileItem.new(filename, file_offset)]
|
|
236
|
+
}]
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
public
|
|
240
|
+
def initialize(filename)
|
|
241
|
+
@filename = filename
|
|
242
|
+
get_items_metadata @filename
|
|
243
|
+
end
|
|
244
|
+
def decompress_file(path)
|
|
245
|
+
File.open(@filename) do |file|
|
|
246
|
+
@items[path].get_decompressed_data file
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
def decompress_all_files
|
|
250
|
+
decompressed_files = []
|
|
251
|
+
File.open(@filename) do |file|
|
|
252
|
+
decompressed_files = @items.keys.map do |path|
|
|
253
|
+
{
|
|
254
|
+
path: path,
|
|
255
|
+
data: @items[path].get_decompressed_data(file)
|
|
256
|
+
}
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
decompressed_files
|
|
260
|
+
end
|
|
261
|
+
def decompress_all_files_to_disk
|
|
262
|
+
dir_of_zip_file = File.dirname(@filename)
|
|
263
|
+
zipfile_without_extension = File.basename @filename, ".*"
|
|
264
|
+
extract_dir_path = "#{dir_of_zip_file}/#{zipfile_without_extension}"
|
|
265
|
+
Dir.mkdir(extract_dir_path) unless Dir.exist?(extract_dir_path)
|
|
266
|
+
decompress_all_files.each do |decompressed_item|
|
|
267
|
+
path = decompressed_item[:path]
|
|
268
|
+
data = decompressed_item[:data]
|
|
269
|
+
file_extract_path = "#{extract_dir_path}/#{path}"
|
|
270
|
+
File.open(file_extract_path, "w") do |extracted_file|
|
|
271
|
+
extracted_file.write data
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pure_ruby_zip
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Edward Halferty
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2020-03-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.17'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.17'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
description: |-
|
|
42
|
+
A pure-Ruby ZIP file decompressor/compressor.
|
|
43
|
+
VERY inefficient, possibly buggy.
|
|
44
|
+
Mostly for entertainment value.
|
|
45
|
+
email:
|
|
46
|
+
- me@edwardhalferty.com
|
|
47
|
+
executables:
|
|
48
|
+
- pure-ruby-zip
|
|
49
|
+
extensions: []
|
|
50
|
+
extra_rdoc_files: []
|
|
51
|
+
files:
|
|
52
|
+
- bin/pure-ruby-zip
|
|
53
|
+
- lib/pure_ruby_zip.rb
|
|
54
|
+
- lib/pure_ruby_zip/version.rb
|
|
55
|
+
homepage: https://github.com/ehalferty/pure_ruby_zip
|
|
56
|
+
licenses:
|
|
57
|
+
- MIT
|
|
58
|
+
metadata: {}
|
|
59
|
+
post_install_message:
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 3.0.8
|
|
75
|
+
signing_key:
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: A pure-Ruby ZIP file decompressor/compressor
|
|
78
|
+
test_files: []
|