/
Zip.pm6
143 lines (110 loc) · 3.86 KB
/
Zip.pm6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use v6;
use experimental :pack;
use File::Zip::EndOfCentralDirectoryHeader;
use File::Zip::CentralDirectoryHeader;
use Compress::Zlib;
=begin markdown
Please see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
=end markdown
unit class File::Zip;
has Str $.file-name is rw;
has Bool $.debug is rw;
has IO::Handle $.fh is rw;
has Int $.eocd-offset is rw;
has $.eocd-header is rw;
has @.cd-headers is rw;
method BUILD(Bool :$debug = False, Str :$file-name) {
self.file-name = $file-name;
self.debug = $debug;
self.fh = $file-name.IO.open(:bin);
my $eocd-offset = self._find-eocd-record-offset;
die "Cannot find EOCD record" if $eocd-offset == -1;
say "eocd offset = $eocd-offset" if self.debug;
my $eocd-header = File::Zip::EndOfCentralDirectoryHeader.new;
$eocd-header.read-from-handle(self.fh, $eocd-offset);
self.eocd-header = $eocd-header;
say "eocd-header = " ~ $eocd-header if self.debug;
self._read-cd-headers;
}
method files {
my @files;
for @.cd-headers -> $cd-header {
@files.push( { filename => $cd-header.file-name } );
}
return @files;
}
method unzip(Str :$directory = '.') {
for @.cd-headers -> $cd-header {
say "Extracting $( $cd-header.file-name )" if self.debug;
self._read-local-file-header($cd-header, $directory);
}
}
=begin markdown
=end markdown
method close {
self.fh.close if self.fh.defined;
}
method _read-local-file-header($cd-header, Str $directory) {
self.fh.seek($cd-header.local-file-header-offset, SeekFromBeginning);
my Buf $local_file_header-buf = self.fh.read(30);
my (
$signature, $version, $general-purpose-bit-flag, $compression-method,
$last-modified-time, $last-modified-date, $crc32, $compressed-size,
$uncompressed-size, $file-name-length, $extra-field-length
) = $local_file_header-buf.unpack("L S S S S S L L L S S");
my Buf $file-name-buf = self.fh.read($file-name-length);
my $output-file-name = $file-name-buf.decode('ascii');
self.fh.seek($extra-field-length, SeekFromCurrent);
if $compression-method == 0x0 {
# Not compressed
if $cd-header.compressed-size > 0 {
my $original = self.fh.read($cd-header.compressed-size);
$directory.IO.mkdir;
"$directory/$output-file-name".IO.spurt($original, :bin);
} else {
"$directory/$output-file-name".IO.mkdir;
#"$directory/$output-file-name".IO.spurt($original, :bin);
}
} elsif $compression-method == 0x08 {
# Deflate compression method
my $compressed = self.fh.read($cd-header.compressed-size);
my $decompressor = Compress::Zlib::Stream.new(:deflate);
my $original = $decompressor.inflate($compressed);
$directory.IO.mkdir;
"$directory/$output-file-name".IO.spurt($original, :bin);
CATCH {
default {
say $_;
}
}
} else {
die "Cannot handle compression method '$compression-method'";
}
}
method _read-cd-headers {
self.fh.seek(self.eocd-header.offset-central-directory, SeekFromBeginning);
my $number-records = self.eocd-header.number-central-directory-records-on-disk;
my @cd-headers;
for 1..$number-records -> $i {
my $cd-header = File::Zip::CentralDirectoryHeader.new;
$cd-header.read-from-handle(self.fh);
@cd-headers.push( $cd-header );
}
self.cd-headers = @cd-headers;
}
=begin markdown
Private method to scan for the end of central directory record signature
starting from the end of the zip file.
=end markdown
method _find-eocd-record-offset {
# Find file size
self.fh.seek(0, SeekFromEnd);
my $file-size = self.fh.tell;
# Find EOCD hexidecimal signature 0x04034b50 in little endian
for 0..$file-size-1 -> $offset {
self.fh.seek(-$offset, SeekFromEnd);
my Buf $bytes = self.fh.read(4);
return $offset if $bytes[0] == 0x50 && $bytes[1] == 0x4b && $bytes[2] == 0x05 && $bytes[3] == 0x06;
}
return -1;
}