File Coverage

blib/lib/Image/ExifTool/7Z.pm
Criterion Covered Total %
statement 13 524 2.4
branch 1 148 0.6
condition 2 9 22.2
subroutine 4 27 14.8
pod 0 24 0.0
total 20 732 2.7


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: 7Z.pm
3             #
4             # Description: Read 7z archive meta information
5             #
6             # Revisions: 2023/04/28 - Amir Gooran (Cyberno)
7             # 2023-05-06 - PH Minor changes in ExifTool interfacing
8             #
9             # References: 1) https://py7zr.readthedocs.io/en/latest/archive_format.html
10             #------------------------------------------------------------------------------
11              
12             package Image::ExifTool::7Z;
13              
14 6     6   39 use strict;
  6         14  
  6         237  
15 6     6   30 use vars qw($VERSION);
  6         11  
  6         258  
16 6     6   27 use Image::ExifTool qw(:DataAccess :Utils);
  6         12  
  6         33372  
17              
18             $VERSION = '1.01';
19              
20             sub ReadUInt32 {
21 0     0 0 0 my $buff;
22              
23 0         0 $_[0]->Read($buff, 4);
24 0         0 my ($output) = unpack('L', $buff);
25 0         0 return $output;
26             }
27              
28             sub ReadUInt64 {
29 0     0 0 0 my $buff;
30             my $output;
31              
32 0         0 $_[0]->Read($buff, 1);
33 0         0 my $b = ord($buff);
34 0 0       0 if($b == 255){ # read real uint64
35 0         0 $_[0]->Read($buff, 8);
36 0         0 my ($output) = unpack('Q', $buff);
37 0         0 return $output;
38             }
39 0         0 my @blen = (0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0xFE);
40              
41 0         0 my $mask = 0x80;
42 0         0 my $vlen = 8;
43              
44 0         0 for (my $l = 0 ; $l < scalar(@blen) ; $l++) {
45 0         0 my $v = $blen[$l];
46 0 0       0 if($b <= $v){
47 0         0 $vlen = $l;
48 0         0 last;
49             }
50 0         0 $mask >>= 1;
51             }
52 0 0       0 if($vlen == 0){
53 0         0 return $b & ($mask - 1);
54             }
55 0         0 $_[0]->Read($buff, $vlen);
56 0         0 $buff .= "\0\0\0\0\0\0\0\0";
57              
58 0         0 my $value = unpack('Q', $buff);
59 0         0 my $highpart = $b & ($mask - 1);
60 0         0 return $value + ($highpart << ($vlen * 8));
61             }
62              
63             sub ReadRealUInt64 {
64 0     0 0 0 my $buff;
65              
66 0         0 $_[0]->Read($buff, 8);
67 0         0 my $value = unpack('Q', $buff);
68 0         0 return $value;
69             }
70              
71             sub ReadBoolean {
72 0     0 0 0 my $buff;
73 0         0 my $count = $_[1];
74 0         0 my $checkall = $_[2];
75 0         0 my @result = ();
76              
77 0 0       0 if($checkall){
78 0         0 $_[0]->Read($buff, 1);
79 0         0 my $all_defined = ord($buff);
80 0 0       0 if($all_defined != 0){
81 0         0 @result = (1)x$count;
82 0         0 return @result;
83             }
84             }
85              
86 0         0 my $b = 0;
87 0         0 my $mask = 0;
88              
89 0         0 for (my $i = 0 ; $i < $count ; $i++) {
90 0 0       0 if($mask == 0){
91 0         0 $_[0]->Read($buff, 1);
92 0         0 $b = ord($buff);
93 0         0 $mask = 0x80;
94             }
95 0         0 push(@result, ($b & $mask) != 0);
96 0         0 $mask >>= 1;
97             }
98 0         0 return @result;
99             }
100              
101             sub ReadUTF16 {
102 0     0 0 0 my $val = "";
103 0         0 my $ch;
104              
105 0         0 for(my $i=0; $i < 65536; $i++){
106 0         0 $_[0]->Read($ch, 2);
107 0 0       0 if($ch eq "\0\0"){
108 0         0 last;
109             }
110 0         0 $val .= $ch;
111             }
112 0         0 return $val;
113             }
114              
115             sub ReadPackInfo {
116 0     0 0 0 my $et = shift;
117              
118 0         0 my $buff;
119 0         0 my %out_packinfo = ();
120 0         0 $out_packinfo{"packsizes"} = ();
121              
122 0         0 $out_packinfo{"packpos"} = ReadUInt64($_[0]);
123 0         0 my $numstreams = ReadUInt64($_[0]);
124 0         0 $et->VPrint(0, "Number Of Streams: $numstreams\n");
125              
126 0         0 $_[0]->Read($buff, 1);
127 0         0 my $pid = ord($buff);
128              
129 0         0 my @packsizes;
130 0 0       0 if($pid == 9){ # size
131 0         0 for (my $i = 0 ; $i < $numstreams ; $i++) {
132 0         0 push(@{ $out_packinfo{"packsizes"} }, ReadUInt64($_[0]));
  0         0  
133             }
134 0         0 $_[0]->Read($buff, 1);
135 0         0 $pid = ord($buff);
136 0 0       0 if($pid == 10){ # crc
137 0         0 my @crcs;
138 0         0 my @digestdefined = ReadBoolean($_[0], $numstreams, 1);
139 0         0 foreach my $crcexist (@digestdefined) {
140 0 0       0 if($crcexist){
141 0         0 push(@crcs, ReadUInt32($_[0]));
142             }
143             }
144 0         0 $_[0]->Read($buff, 1);
145 0         0 $pid = ord($buff);
146             }
147             }
148 0 0       0 if($pid != 0) { # end id expected
149 0         0 return 0;
150             }
151 0         0 return \%out_packinfo;
152             }
153              
154             sub findInBinPair {
155 0     0 0 0 my @bindpairs = @{$_[0]};
  0         0  
156 0         0 my $index = $_[1];
157              
158 0         0 for (my $i = 0; $i < scalar(@bindpairs); $i++) {
159 0 0       0 if($bindpairs[$i] == $index){
160 0         0 return $i;
161             }
162             }
163 0         0 return -1;
164             }
165              
166             sub ReadFolder {
167 0     0 0 0 my $et = shift;
168 0         0 my $buff;
169 0         0 my $totalin = 0;
170 0         0 my $totalout = 0;
171 0         0 my %out_folder = ();
172 0         0 $out_folder{"packed_indices"} = ();
173 0         0 $out_folder{"bindpairs"} = ();
174 0         0 $out_folder{"coders"} = ();
175              
176 0         0 my $num_coders = ReadUInt64($_[0]);
177 0         0 $et->VPrint(0, "Number of coders: $num_coders\n");
178              
179 0         0 for (my $i = 0; $i < $num_coders; $i++) {
180 0         0 my %c = ();
181 0         0 $_[0]->Read($buff, 1);
182 0         0 my $b = ord($buff);
183 0         0 my $methodsize = $b & 0xF;
184 0         0 my $iscomplex = ($b & 0x10) == 0x10;
185 0         0 my $hasattributes = ($b & 0x20) == 0x20;
186 0 0       0 if($methodsize > 0){
187 0         0 $_[0]->Read($buff, $methodsize);
188 0         0 $c{"method"} = $buff;
189             }
190             else{
191 0         0 $c{"method"} = "\0";
192             }
193 0 0       0 if($iscomplex){
194 0         0 $c{"numinstreams"} = ReadUInt64($_[0]);
195 0         0 $c{"numoutstreams"} = ReadUInt64($_[0]);
196             }
197             else{
198 0         0 $c{"numinstreams"} = 1;
199 0         0 $c{"numoutstreams"} = 1;
200             }
201 0         0 $totalin += $c{"numinstreams"};
202 0         0 $totalout += $c{"numoutstreams"};
203 0 0       0 if($hasattributes){
204 0         0 my $proplen = ReadUInt64($_[0]);
205 0         0 $_[0]->Read($buff, $proplen);
206 0         0 $c{"properties"} = $buff;
207             }
208             else {
209 0         0 $c{"properties"} = undef;
210             }
211 0         0 $et->VPrint(0, "Reading coder $i\n");
212 0         0 push(@{ $out_folder{"coders"} }, \%c);
  0         0  
213             }
214 0         0 my $num_bindpairs = $totalout - 1;
215 0         0 for (my $i = 0; $i < $num_bindpairs; $i++) {
216 0         0 my @bond = (ReadUInt64($_[0]), ReadUInt64($_[0]));
217 0         0 push(@{ $out_folder{"bindpairs"} }, @bond);
  0         0  
218             }
219 0         0 my $num_packedstreams = $totalin - $num_bindpairs;
220 0 0       0 if($num_packedstreams == 1){
221 0         0 for (my $i = 0; $i < $totalin; $i++) {
222 0 0       0 if(findInBinPair(\@{ $out_folder{"bindpairs"} }, $i) < 0){
  0         0  
223 0         0 push(@{ $out_folder{"packed_indices"} }, $i);
  0         0  
224             }
225             }
226             }
227             else{
228 0         0 for (my $i = 0; $i < $num_packedstreams; $i++) {
229 0         0 push(@{ $out_folder{"packed_indices"} }, ReadUInt64($_[0]));
  0         0  
230             }
231             }
232              
233 0         0 return \%out_folder;
234             }
235              
236             sub RetrieveCodersInfo{
237 0     0 0 0 my $et = shift;
238 0         0 my $buff;
239 0         0 my @folders = @{ $_[1] };
  0         0  
240              
241 0         0 $_[0]->Read($buff, 1);
242 0         0 my $pid = ord($buff);
243              
244 0 0       0 if($pid != 0x0c){ # coders unpack size id expected
245 0         0 return 0;
246             }
247 0         0 foreach my $folder (@folders) {
248 0         0 $folder->{"unpacksizes"} = ();
249 0         0 foreach my $c (@{ $folder->{"coders"} }) {
  0         0  
250 0         0 for (my $i = 0 ; $i < $c->{"numoutstreams"} ; $i++) {
251 0         0 push(@{ $folder->{"unpacksizes" } }, ReadUInt64($_[0]));
  0         0  
252             }
253             }
254             }
255 0         0 $_[0]->Read($buff, 1);
256 0         0 $pid = ord($buff);
257              
258 0 0       0 if($pid == 0x0a){ #crc
259 0         0 my $numfolders = scalar(@folders);
260 0         0 $et->VPrint(0, "Number of folders: $numfolders\n");
261 0         0 my @defined = ReadBoolean($_[0], $numfolders, 1);
262 0         0 my @crcs;
263 0         0 foreach my $crcexist (@defined) {
264 0 0       0 if($crcexist){
265 0         0 push(@crcs, ReadUInt32($_[0]));
266             }
267             }
268 0         0 for (my $i = 0 ; $i < $numfolders ; $i++) {
269 0         0 $folders[$i]->{"digestdefined"} = $defined[$i];
270 0         0 $folders[$i]->{"crc"} = $crcs[$i];
271             }
272 0         0 $_[0]->Read($buff, 1);
273 0         0 $pid = ord($buff);
274             }
275              
276 0 0       0 if($pid != 0x00){ # end id expected
277 0         0 $et->VPrint(0, "Invalid PID: $pid\n");
278 0         0 return 0;
279             }
280 0         0 return 1;
281             }
282              
283             sub ReadUnpackInfo {
284 0     0 0 0 my $et = shift;
285 0         0 my $buff;
286 0         0 my %out_unpackinfo = ();
287              
288 0         0 $_[0]->Read($buff, 1);
289 0         0 my $pid = ord($buff);
290              
291 0 0       0 if($pid != 0xb) { # folder id expected
292 0         0 return 0;
293             }
294              
295 0         0 $out_unpackinfo{"numfolders"} = ReadUInt64($_[0]);
296 0         0 $out_unpackinfo{"folders"} = ();
297              
298 0         0 $_[0]->Read($buff, 1);
299 0         0 my $external = ord($buff);
300              
301 0 0       0 if($external == 0x00){
302 0         0 for (my $i = 0 ; $i < $out_unpackinfo{"numfolders"}; $i++) {
303 0         0 $et->VPrint(0, "Reading folder $i\n");
304 0         0 my $folder = ReadFolder($et, $_[0]);
305 0         0 push(@{ $out_unpackinfo{"folders"} }, $folder);
  0         0  
306             }
307             }
308 0 0       0 return 0 unless RetrieveCodersInfo($et, $_[0], $out_unpackinfo{"folders"});
309 0         0 return \%out_unpackinfo;
310             }
311              
312             sub ReadSubstreamsInfo {
313 0     0 0 0 my $et = shift;
314 0         0 my $buff;
315 0         0 my %out_substreamsinfo = ();
316 0         0 $out_substreamsinfo{"num_unpackstreams_folders"} = ();
317              
318 0         0 my $numfolders = $_[1];
319 0         0 my $folders = $_[2];
320              
321 0         0 $_[0]->Read($buff, 1);
322 0         0 my $pid = ord($buff);
323 0 0       0 if($pid == 13){ # num unpack stream
324 0         0 $et->VPrint(0, "Num unpack stream detected.\n");
325 0         0 for (my $i = 0 ; $i < $numfolders; $i++) {
326 0         0 push(@{ $out_substreamsinfo{"num_unpackstreams_folders"} }, ReadUInt64($_[0]));
  0         0  
327             }
328 0         0 $_[0]->Read($buff, 1);
329 0         0 $pid = ord($buff);
330             }
331             else{
332 0         0 @{ $out_substreamsinfo{"num_unpackstreams_folders"} } = (1)x$numfolders;
  0         0  
333             }
334 0 0       0 if($pid == 9){ # size property
335 0         0 $et->VPrint(0, "Size property detected.\n");
336 0         0 $out_substreamsinfo{"unpacksizes"} = ();
337 0         0 for(my $i=0; $i< scalar(@{ $out_substreamsinfo{"num_unpackstreams_folders"} }); $i++){
  0         0  
338 0         0 my $totalsize = 0;
339 0         0 for(my $j=1; $j < @{ $out_substreamsinfo{"num_unpackstreams_folders"} }[$i]; $j++){
  0         0  
340 0         0 my $size = ReadUInt64($_[0]);
341 0         0 push(@{ $out_substreamsinfo{"unpacksizes"} }, $size);
  0         0  
342 0         0 $totalsize += $size;
343             }
344             # self.unpacksizes.append(folders[i].get_unpack_size() - totalsize)
345             }
346 0         0 $_[0]->Read($buff, 1);
347 0         0 $pid = ord($buff);
348             }
349 0         0 my $num_digests = 0;
350 0         0 my $num_digests_total = 0;
351 0         0 for (my $i = 0 ; $i < $numfolders; $i++) {
352 0         0 my $numsubstreams = @{ $out_substreamsinfo{"num_unpackstreams_folders"} }[$i];
  0         0  
353 0 0 0     0 if($numsubstreams != 1 or not @{ $folders }[$i]->{"digestdefined"}){
  0         0  
354 0         0 $num_digests += $numsubstreams;
355             }
356 0         0 $num_digests_total += $numsubstreams;
357             }
358 0         0 $et->VPrint(0, "Num Digests Total: $num_digests_total\n");
359 0 0       0 if($pid == 10) { # crc property
360 0         0 $et->VPrint(0, "CRC property detected.\n");
361 0         0 my @crcs;
362 0         0 my @defined = ReadBoolean($_[0], $num_digests, 1);
363 0         0 foreach my $crcexist (@defined) {
364 0         0 push(@crcs, ReadUInt32($_[0]));
365             }
366 0         0 $_[0]->Read($buff, 1);
367 0         0 $pid = ord($buff);
368             }
369 0 0       0 if($pid != 0x00){ # end id expected
370 0         0 return 0;
371             }
372 0         0 return \%out_substreamsinfo;
373             }
374              
375             sub ReadStreamsInfo {
376 0     0 0 0 my $et = shift;
377 0         0 my $buff;
378             my $unpackinfo;
379 0         0 my %out_streamsinfo = ();
380              
381 0         0 $_[0]->Read($buff, 1);
382 0         0 my $pid = ord($buff);
383 0 0       0 if($pid == 6){ # pack info
384 0         0 my $packinfo = ReadPackInfo($et, $_[0]);
385 0 0       0 return 0 unless $packinfo;
386 0         0 $out_streamsinfo{"packinfo"} = $packinfo;
387 0         0 $_[0]->Read($buff, 1);
388 0         0 $pid = ord($buff);
389             }
390 0 0       0 if($pid == 7) { # unpack info
391 0         0 $et->VPrint(0, "Unpack info data detected.\n");
392 0         0 $unpackinfo = ReadUnpackInfo($et, $_[0]);
393 0 0       0 return 0 unless $unpackinfo;
394 0         0 $out_streamsinfo{"unpackinfo"} = $unpackinfo;
395 0         0 $_[0]->Read($buff, 1);
396 0         0 $pid = ord($buff);
397             }
398 0 0       0 if($pid == 8){ # substreams info
399 0         0 $et->VPrint(0, "Substreams info data detected.\n");
400 0         0 my $substreamsinfo = ReadSubstreamsInfo($et, $_[0], $unpackinfo->{"numfolders"}, $unpackinfo->{"folders"});
401 0 0       0 return 0 unless $substreamsinfo;
402 0         0 $out_streamsinfo{"substreamsinfo"} = $substreamsinfo;
403 0         0 $_[0]->Read($buff, 1);
404 0         0 $pid = ord($buff);
405             }
406 0 0       0 if($pid != 0x00){ # end id expected
407 0         0 $et->VPrint(0, "Invalid PID: $pid\n");
408 0         0 return 0;
409             }
410 0         0 return \%out_streamsinfo;
411             }
412              
413             sub IsNativeCoder {
414 0     0 0 0 my $coder = $_[0];
415              
416 0 0       0 if(ord(substr($coder->{"method"}, 0, 1)) == 3){
    0          
417 0 0       0 if(ord(substr($coder->{"method"}, 1, 1)) == 1) {
418 0 0       0 if(ord(substr($coder->{"method"}, 2, 1)) == 1) {
419 0         0 return "LZMA";
420             }
421             }
422             }
423             elsif(ord(substr($coder->{"method"}, 0, 1)) == 6){
424 0 0       0 if(ord(substr($coder->{"method"}, 1, 1)) == 0xf1) {
425 0 0       0 if(ord(substr($coder->{"method"}, 2, 1)) == 7) {
426 0 0       0 if(ord(substr($coder->{"method"}, 3, 1)) == 1) {
427 0         0 return "7zAES";
428             }
429             }
430             }
431             }
432             }
433              
434             sub GetDecompressor {
435 0     0 0 0 my $et = shift;
436              
437 0         0 my $folder = $_[0];
438 0         0 my %out_decompressor = ();
439 0         0 $out_decompressor{"chain"} = ();
440 0         0 $out_decompressor{"input_size"} = $_[1];
441 0         0 $out_decompressor{"_unpacksizes"} = $folder->{"unpacksizes"};
442 0         0 @{ $out_decompressor{"_unpacked"} } = (0) x scalar(@{ $out_decompressor{"_unpacksizes"} });
  0         0  
  0         0  
443 0         0 $out_decompressor{"consumed"} = 0;
444 0         0 $out_decompressor{"block_size"} = 32768;
445 0         0 $out_decompressor{"_unused"} = [];
446              
447 0         0 foreach my $coder (@{ $folder->{"coders"} }) {
  0         0  
448 0         0 my $algorithm = IsNativeCoder($coder);
449 0 0       0 if($algorithm eq "7zAES") {
450 0         0 $et->Warn("File is encrypted.", 0);
451 0         0 return 0;
452             }
453             else{
454 0         0 push(@{ $out_decompressor{"chain"} }, $algorithm);
  0         0  
455             }
456             }
457              
458 0         0 return \%out_decompressor;
459             }
460              
461             sub ReadData {
462 0     0 0 0 my $et = shift;
463 0         0 my $decompressor = $_[1];
464 0         0 my $rest_size = $decompressor->{"input_size"} - $decompressor->{"consumed"};
465 0         0 my $unused_s = scalar(@{ $decompressor->{"_unused"} });
  0         0  
466 0         0 my $read_size = $rest_size - $unused_s;
467 0         0 my $data = "";
468 0 0       0 if($read_size > $decompressor->{"block_size"} - $unused_s){
469 0         0 $read_size = $decompressor->{"block_size"} - $unused_s;
470             }
471 0 0       0 if($read_size > 0){
472 0         0 $decompressor->{"consumed"} += $_[0]->Read($data, $read_size);
473 0         0 $et->VPrint(0, "Compressed size: $read_size\n");
474             }
475 0         0 return $data;
476             }
477              
478             sub Decompress_Internal {
479 0     0 0 0 my $data = "";
480 0         0 for(my $i=0; $i < scalar(@{ $_[0]->{"chain"} }); $i++){
  0         0  
481 0 0       0 if(@{ $_[0]->{"_unpacked"} }[$i] < @{ $_[0]->{"_unpacksizes"} }[$i]){
  0         0  
  0         0  
482 0         0 my %opts = ();
483 0         0 $opts{"Filter"} = Lzma::Filter::Lzma1();
484 0         0 my ($z, $status) = Compress::Raw::Lzma::RawDecoder->new( %opts );
485 0         0 $status = $z->code($_[1], $data);
486 0         0 @{ $_[0]->{"_unpacked"} }[$i] += length($data);
  0         0  
487             }
488             }
489 0         0 return $data;
490             }
491              
492             sub Decompress {
493 0     0 0 0 my $et = shift;
494 0         0 my $max_length = $_[1];
495 0         0 my $data = ReadData($et, $_[0], $_[1]);
496 0         0 my $tmp = Decompress_Internal($_[1], $data);
497 0         0 return $tmp;
498             }
499              
500             sub ReadName {
501 0     0 0 0 my $numfiles = $_[1];
502              
503 0         0 for(my $i=0; $i < $numfiles; $i++){
504 0         0 @{ $_[2] }[$i]->{"filename"} = ReadUTF16($_[0]);
  0         0  
505             }
506             }
507              
508             sub ReadTimes {
509 0     0 0 0 my $et = shift;
510 0         0 my $external;
511 0         0 my $numfiles = $_[1];
512 0         0 my $name = $_[2];
513              
514 0         0 my @defined = ReadBoolean($_[0], $numfiles, 1);
515 0         0 $_[0]->Read($external, 1);
516 0 0       0 if(ord($external) != 0){
517 0         0 $et->Warn("Invalid or corrupted file. (ReadTimes)");
518 0         0 return 0;
519             }
520              
521 0         0 for(my $i=0; $i < $numfiles; $i++){
522 0 0       0 if($defined[$i]){
523 0         0 my $value = ReadRealUInt64($_[0]);
524 0         0 $value = $value / 10000000.0 - 11644473600;
525 0         0 @{ $_[3] }[$i]->{$name} = $value;
  0         0  
526             }
527             else{
528 0         0 @{ $_[3] }[$i]->{$name} = undef;
  0         0  
529             }
530             }
531             }
532              
533             sub ReadAttributes {
534 0     0 0 0 my $numfiles = $_[1];
535              
536 0         0 for(my $i=0; $i < $numfiles; $i++){
537 0 0       0 if($_[2][$i]){
538 0         0 my $value = ReadUInt32($_[0]);
539 0         0 @{ $_[3] }[$i]->{"attributes"} = $value >> 8;
  0         0  
540             }
541             else{
542 0         0 @{ $_[3] }[$i]->{"attributes"} = undef;
  0         0  
543             }
544             }
545             }
546              
547             sub ReadFilesInfo {
548 0     0 0 0 my $et = shift;
549 0         0 my $buff;
550              
551 0         0 my $numfiles = ReadUInt64($_[0]);
552 0         0 my @out_files = ();
553 0         0 for(my $i = 0; $i < $numfiles; $i++){
554 0         0 my %new_file = ();
555 0         0 $new_file{"emptystream"} = 0;
556 0         0 push(@out_files, \%new_file);
557             }
558 0         0 my $numemptystreams = 0;
559 0         0 $et->VPrint(0, "Number of files: $numfiles\n");
560 0         0 while(1){
561 0         0 $_[0]->Read($buff, 1);
562 0         0 my $prop = ord($buff);
563 0 0       0 if($prop == 0){ # end
564 0         0 return \@out_files;
565             }
566 0         0 my $size = ReadUInt64($_[0]);
567 0 0       0 if($prop == 25) { # dummy
568 0         0 $_[0]->Seek($size, 1);
569 0         0 next;
570             }
571 0         0 $_[0]->Read($buff, $size);
572 0         0 my $buffer = File::RandomAccess->new(\$buff);
573 0 0       0 if($prop == 14){ # empty stream
    0          
    0          
    0          
    0          
574 0         0 my @isempty = ReadBoolean($buffer, $numfiles, 0);
575 0         0 my $numemptystreams = 0;
576 0         0 for(my $i = 0; $i < $numfiles; $i++){
577 0 0       0 if($isempty[$i] == 0){
578 0         0 $out_files[$i]->{"emptystream"} = 0;
579             }
580             else{
581 0         0 $out_files[$i]->{"emptystream"} = 1;
582 0         0 $numemptystreams++;
583             }
584             }
585             }
586             elsif($prop == 15) { # empty file
587              
588             }
589             elsif($prop == 17){ # name
590 0         0 $et->VPrint(0, "Name prop detected.\n");
591 0         0 my $external;
592 0         0 $buffer->Read($external, 1);
593 0         0 my $is_external = ord($external);
594 0 0       0 if($is_external == 0){
595 0         0 ReadName($buffer, $numfiles, \@out_files);
596             }
597             }
598             elsif($prop == 20){ # last write time
599 0         0 $et->VPrint(0, "Last write time detected.\n");
600 0         0 ReadTimes($et, $buffer, $numfiles, "lastwritetime", \@out_files);
601             }
602             elsif($prop == 21){ # attributes
603 0         0 $et->VPrint(0, "File attributes detected.\n");
604 0         0 my $external;
605 0         0 my @defined = ReadBoolean($buffer, $numfiles, 1);
606 0         0 $_[0]->Read($external, 1);
607 0 0       0 if(ord($external) == 0){
608 0         0 ReadAttributes($buffer, $numfiles, \@defined, \@out_files);
609             }
610             else{
611 0         0 my $dataindex = ReadUINT64($buffer);
612             #TODO: try to read external data
613             }
614             }
615             }
616             }
617              
618             sub ExtractHeaderInfo {
619 0     0 0 0 my $et = shift;
620 0         0 my $buff;
621 0         0 my %out_headerinfo = ();
622 0         0 $out_headerinfo{"files_info"} = ();
623 0         0 my $files_info;
624              
625 0         0 $_[0]->Read($buff, 1);
626 0         0 my $pid = ord($buff);
627              
628 0 0       0 if($pid == 0x04){
629 0         0 my $mainstreams = ReadStreamsInfo($et, $_[0]);
630 0 0       0 if($mainstreams == 0){
631 0         0 $et->Warn("Invalid or corrupted file. (ExtractHeaderInfo)");
632 0         0 return 0;
633             }
634 0         0 $_[0]->Read($buff, 1);
635 0         0 $pid = ord($buff);
636             }
637 0 0       0 if($pid == 0x05){
638 0         0 $et->VPrint(0, "File info pid reached.\n");
639 0         0 $files_info = ReadFilesInfo($et, $_[0]);
640 0         0 push(@{ $out_headerinfo{"files_info"} }, $files_info);
  0         0  
641 0         0 $_[0]->Read($buff, 1);
642 0         0 $pid = ord($buff);
643             }
644 0 0       0 if($pid != 0x00){ # end id expected
645 0         0 $et->VPrint(0, "Invalid PID: $pid\n");
646 0         0 return 0;
647             }
648 0         0 return \%out_headerinfo;
649             }
650              
651             sub DisplayFiles {
652 0     0 0 0 my $et = shift;
653 0         0 my $docNum = 0;
654 0         0 my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5');
655              
656 0         0 foreach my $currentfile (@{ $_[0] }){
  0         0  
657 0         0 $$et{DOC_NUM} = ++$docNum;
658 0         0 $et->HandleTag($tagTablePtr, 'ModifyDate', $currentfile->{"lastwritetime"});
659 0         0 $et->HandleTag($tagTablePtr, 'ArchivedFileName', $currentfile->{"filename"});
660             }
661 0         0 delete $$et{DOC_NUM};
662 0 0 0     0 if($docNum > 1 and not $et->Options('Duplicates')){
663 0         0 $et->Warn("Use the Duplicates option to extract tags for all $docNum files", 1);
664             }
665             }
666              
667             #------------------------------------------------------------------------------
668             # Extract information from a 7z file
669             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
670             # Returns: 1 on success, 0 if this wasn't a valid 7z file
671             sub Process7Z($$)
672             {
673 14     14 0 44 my ($et, $dirInfo) = @_;
674 14         41 my $raf = $$dirInfo{RAF};
675 14         31 my ($flags, $buff);
676              
677 14 50 66     79 return 0 unless $raf->Read($buff, 6) and $buff eq "7z\xbc\xaf\x27\x1c";
678              
679 0           $et->SetFileType();
680              
681 0           $raf->Read($buff, 2);
682 0           my ($major_version, $minor_version) = unpack('cc', $buff);
683 0           my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5');
684 0           $et->HandleTag($tagTablePtr, 'FileVersion', sprintf('7z v%d.%.2d',$major_version,$minor_version));
685              
686 0           $raf->Seek(4, 1); # skip Start Header CRC
687              
688 0           $raf->Read($buff, 20);
689 0           my ($nextheaderoffset, $nextheadersize) = unpack('QQx', $buff);
690 0           $et->VPrint(0, "NextHeaderOffset: $nextheaderoffset, NextHeaderSize: $nextheadersize\n");
691              
692 0           $raf->Seek($nextheaderoffset, 1); # going to next header offset
693 0           $raf->Read($buff, 1);
694 0           my $pid = ord($buff);
695 0 0         if($pid == 1){ # normal header
    0          
696 0           $et->VPrint(0,"Normal header detected. trying to decode\n");
697 0           my $headerinfo = ExtractHeaderInfo($et, $raf);
698 0 0         if($headerinfo == 0){
699 0           $et->Warn("Invalid or corrupted file.");
700 0           return 1;
701             }
702 0           DisplayFiles($et, @{ $headerinfo->{"files_info"} });
  0            
703             }
704             elsif($pid == 23){ # encoded header
705 0 0         unless (eval { require Compress::Raw::Lzma }) {
  0            
706 0           $et->Warn("Install Compress::Raw::Lzma to read encoded 7z information");
707 0           return 1;
708             }
709 0           $et->VPrint(0, "Encoded Header detected. trying to decode\n");
710 0           my $streamsinfo = ReadStreamsInfo($et, $raf);
711 0 0         if($streamsinfo == 0){
712 0           $et->Warn("Invalid or corrupted file.");
713 0           return 1;
714             }
715 0           my $buffer2 = ();
716 0           foreach my $folder (@{ $streamsinfo->{"unpackinfo"}->{"folders"} }) {
  0            
717 0           my @uncompressed = @{ $folder->{"unpacksizes"} };
  0            
718 0           my $compressed_size = $streamsinfo->{"packinfo"}->{"packsizes"}[0];
719 0           my $uncompressed_size = @uncompressed[scalar(@uncompressed) - 1];
720 0           my $decomporessor = GetDecompressor($et, $folder, $compressed_size);
721 0 0         if($decomporessor == 0){
722 0           $et->Warn("Invalid or corrupted file.");
723 0           return 1;
724             }
725              
726 0           my $src_start = 32;
727 0           $src_start += $streamsinfo->{"packinfo"}->{"packpos"};
728 0           $raf->Seek($src_start, 0);
729 0           my $remaining = $uncompressed_size;
730 0           my $folder_data = "";
731 0           while($remaining > 0){
732 0           $folder_data .= Decompress($et, $raf, $decomporessor, $remaining);
733 0           $remaining = $uncompressed_size - length($folder_data);
734             }
735 0           $buffer2 = File::RandomAccess->new(\$folder_data);
736             }
737 0           $buffer2->Seek(0, 0);
738 0           $buffer2->Read($buff, 1);
739 0           $pid = ord($buff);
740 0 0         if($pid != 0x01){ # header field expected
741 0           return 0;
742             }
743 0           my $headerinfo = ExtractHeaderInfo($et, $buffer2);
744 0 0         if($headerinfo == 0){
745 0           $et->Warn("Invalid or corrupted file.");
746 0           return 1;
747             }
748 0           DisplayFiles($et, @{ $headerinfo->{"files_info"} });
  0            
749             }else{ # Unknown header
750 0           return 0;
751             }
752              
753 0           return 1;
754             }
755              
756             1; # end
757              
758             __END__