1-- Legal licensing note: 2 3-- Copyright (c) 1999 .. 2018 Gautier de Montmollin 4-- SWITZERLAND 5 6-- Permission is hereby granted, free of charge, to any person obtaining a copy 7-- of this software and associated documentation files (the "Software"), to deal 8-- in the Software without restriction, including without limitation the rights 9-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10-- copies of the Software, and to permit persons to whom the Software is 11-- furnished to do so, subject to the following conditions: 12 13-- The above copyright notice and this permission notice shall be included in 14-- all copies or substantial portions of the Software. 15 16-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22-- THE SOFTWARE. 23 24-- NB: this is the MIT License, as found 12-Sep-2007 on the site 25-- http://www.opensource.org/licenses/mit-license.php 26 27with Zip.Headers; 28 29with Ada.Characters.Handling; 30with Ada.Unchecked_Deallocation; 31with Ada.Exceptions; use Ada.Exceptions; 32with Ada.IO_Exceptions; 33with Ada.Strings.Fixed; 34with Ada.Strings.Unbounded; 35 36package body Zip is 37 38 use Interfaces; 39 40 procedure Dispose is new Ada.Unchecked_Deallocation( Dir_node, p_Dir_node ); 41 procedure Dispose is new Ada.Unchecked_Deallocation( String, p_String ); 42 43 package Binary_tree_rebalancing is 44 procedure Rebalance( root: in out p_Dir_node ); 45 end Binary_tree_rebalancing; 46 47 package body Binary_tree_rebalancing is 48 49 ------------------------------------------------------------------- 50 -- Tree Rebalancing in Optimal Time and Space -- 51 -- QUENTIN F. STOUT and BETTE L. WARREN -- 52 -- Communications of the ACM September 1986 Volume 29 Number 9 -- 53 ------------------------------------------------------------------- 54 -- http://www.eecs.umich.edu/~qstout/pap/CACM86.pdf 55 -- 56 -- Translated by (New) P2Ada v. 15-Nov-2006 57 58 procedure Tree_to_vine( root: p_Dir_node; size: out Integer ) 59 -- transform the tree with pseudo-root 60 -- "root^" into a vine with pseudo-root 61 -- node "root^", and store the number of 62 -- nodes in "size" 63 is 64 vine_tail, remainder, temp: p_Dir_node; 65 begin 66 vine_tail := root; 67 remainder := vine_tail.right; 68 size := 0; 69 while remainder /= null loop 70 if remainder.left = null then 71 -- move vine-tail down one: 72 vine_tail := remainder; 73 remainder := remainder.right; 74 size := size + 1; 75 else 76 -- rotate: 77 temp := remainder.left; 78 remainder.left := temp.right; 79 temp.right := remainder; 80 remainder := temp; 81 vine_tail.right := temp; 82 end if; 83 end loop; 84 end Tree_to_vine; 85 86 procedure Vine_to_tree( root: p_Dir_node; size_given: Integer ) is 87 -- convert the vine with "size" nodes and pseudo-root 88 -- node "root^" into a balanced tree 89 leaf_count: Integer; 90 size : Integer:= size_given; 91 92 procedure Compression( root_compress: p_Dir_node; count: Integer ) is 93 -- Compress "count" spine nodes in the tree with pseudo-root "root_compress^" 94 scanner, child: p_Dir_node; 95 begin 96 scanner := root_compress; 97 for counter in reverse 1 .. count loop 98 child := scanner.right; 99 scanner.right := child.right; 100 scanner := scanner.right; 101 child.right := scanner.left; 102 scanner.left := child; 103 end loop; 104 end Compression; 105 106 -- Returns n - 2 ** Integer( Float'Floor( log( Float(n) ) / log(2.0) ) ) 107 -- without Float-Point calculation and rounding errors with too short floats 108 function Remove_leading_binary_1( n: Integer ) return Integer is 109 x: Integer:= 2**16; -- supposed maximum 110 begin 111 if n < 1 then 112 return n; 113 end if; 114 while n mod x = n loop 115 x:= x / 2; 116 end loop; 117 return n mod x; 118 end Remove_leading_binary_1; 119 120 begin -- Vine_to_tree 121 leaf_count := Remove_leading_binary_1(size + 1); 122 Compression(root, leaf_count); -- create deepest leaves 123 -- use Perfect_leaves instead for a perfectly balanced tree 124 size := size - leaf_count; 125 while size > 1 loop 126 Compression(root, size / 2); 127 size := size / 2; 128 end loop; 129 end Vine_to_tree; 130 131 procedure Rebalance( root: in out p_Dir_node ) is 132 -- Rebalance the binary search tree with root "root.all", 133 -- with the result also rooted at "root.all". 134 -- Uses the Tree_to_vine and Vine_to_tree procedures. 135 pseudo_root: p_Dir_node; 136 size: Integer; 137 begin 138 pseudo_root:= new Dir_node(name_len => 0); 139 pseudo_root.right := root; 140 Tree_to_vine(pseudo_root, size); 141 Vine_to_tree(pseudo_root, size); 142 root := pseudo_root.right; 143 Dispose(pseudo_root); 144 end Rebalance; 145 146 end Binary_tree_rebalancing; 147 148 -- 19-Jun-2001: Enhanced file name identification 149 -- a) when case insensitive -> all UPPER (current) 150 -- b) '\' and '/' identified -> all '/' (new) 151 152 function Normalize( s: String; case_sensitive: Boolean ) return String is 153 sn: String( s'Range ); 154 begin 155 if case_sensitive then 156 sn:= s; 157 else 158 sn:= Ada.Characters.Handling.To_Upper(s); 159 end if; 160 for i in sn'Range loop 161 if sn(i)= '\' then 162 sn(i):='/'; 163 end if; 164 end loop; 165 return sn; 166 end Normalize; 167 168 boolean_to_encoding: constant array(Boolean) of Zip_name_encoding:= 169 (False => IBM_437, True => UTF_8); 170 171 ------------------------------------------------------------- 172 -- Load Zip_info from a stream containing the .zip archive -- 173 ------------------------------------------------------------- 174 175 procedure Load( 176 info : out Zip_info; 177 from : in out Zip_Streams.Root_Zipstream_Type'Class; 178 case_sensitive : in Boolean:= False; 179 duplicate_names : in Duplicate_name_policy:= error_on_duplicate 180 ) 181 is 182 procedure Insert( 183 dico_name : String; -- UPPER if case-insensitive search 184 file_name : String; 185 file_index : Zip_Streams.ZS_Index_Type; 186 comp_size, 187 uncomp_size : File_size_type; 188 crc_32 : Unsigned_32; 189 date_time : Time; 190 method : PKZip_method; 191 name_encoding : Zip_name_encoding; 192 read_only : Boolean; 193 encrypted_2_x : Boolean; 194 root_node : in out p_Dir_node 195 ) 196 is 197 procedure Insert_into_tree(node: in out p_Dir_node) is 198 begin 199 if node = null then 200 node:= new Dir_node' 201 ( (name_len => file_name'Length, 202 left => null, 203 right => null, 204 dico_name => dico_name, 205 file_name => file_name, 206 file_index => file_index, 207 comp_size => comp_size, 208 uncomp_size => uncomp_size, 209 crc_32 => crc_32, 210 date_time => date_time, 211 method => method, 212 name_encoding => name_encoding, 213 read_only => read_only, 214 encrypted_2_x => encrypted_2_x, 215 user_code => 0 216 ) 217 ); 218 elsif dico_name > node.dico_name then 219 Insert_into_tree(node.right); 220 elsif dico_name < node.dico_name then 221 Insert_into_tree(node.left); 222 else 223 -- Here we have a case where the entry name already exists in the dictionary. 224 case duplicate_names is 225 when error_on_duplicate => 226 Ada.Exceptions.Raise_Exception 227 (Duplicate_name'Identity, 228 "Same full entry name (in dictionary: " & dico_name & 229 ") appears twice in archive directory; " & 230 "procedure Load was called with strict name policy." 231 ); 232 when admit_duplicates => 233 if file_index > node.file_index then 234 Insert_into_tree(node.right); 235 elsif file_index < node.file_index then 236 Insert_into_tree(node.left); 237 else 238 Ada.Exceptions.Raise_Exception 239 (Duplicate_name'Identity, 240 "Archive directory corrupt: same full entry name (in dictionary: " & 241 dico_name & "), with same data position, appear twice." 242 ); 243 end if; 244 end case; 245 end if; 246 end Insert_into_tree; 247 -- 248 begin 249 Insert_into_tree(root_node); 250 end Insert; 251 252 the_end: Zip.Headers.End_of_Central_Dir; 253 header : Zip.Headers.Central_File_Header; 254 p : p_Dir_node:= null; 255 zip_info_already_loaded: exception; 256 main_comment: p_String; 257 begin -- Load Zip_info 258 if info.loaded then 259 raise zip_info_already_loaded; 260 end if; -- 15-Apr-2002 261 Zip.Headers.Load(from, the_end); 262 -- We take the opportunity to read the main comment, which is right 263 -- after the end-of-central-directory block. 264 main_comment:= new String(1..Integer(the_end.main_comment_length)); 265 String'Read(from'Access, main_comment.all); 266 -- Process central directory: 267 Zip_Streams.Set_Index( 268 from, 269 Zip_Streams.ZS_Index_Type(1 + the_end.central_dir_offset) + the_end.offset_shifting 270 ); 271 272 for i in 1..the_end.total_entries loop 273 Zip.Headers.Read_and_check(from, header ); 274 declare 275 this_name: String(1..Natural(header.short_info.filename_length)); 276 use Zip_Streams; 277 begin 278 String'Read(from'Access, this_name); 279 -- Skip extra field and entry comment. 280 Set_Index( 281 from, 282 Index( from ) + 283 ZS_Size_Type ( 284 header.short_info.extra_field_length + 285 header.comment_length 286 ) 287 ); 288 -- Now the whole i_th central directory entry is behind 289 Insert( dico_name => Normalize(this_name, case_sensitive), 290 file_name => Normalize(this_name, True), 291 file_index => Zip_Streams.ZS_Index_Type (1 + header.local_header_offset) + 292 the_end.offset_shifting, 293 comp_size => header.short_info.dd.compressed_size, 294 uncomp_size => header.short_info.dd.uncompressed_size, 295 crc_32 => header.short_info.dd.crc_32, 296 date_time => header.short_info.file_timedate, 297 method => Method_from_code(header.short_info.zip_type), 298 name_encoding => 299 boolean_to_encoding( 300 (header.short_info.bit_flag and 301 Zip.Headers.Language_Encoding_Flag_Bit) /= 0), 302 read_only => header.made_by_version / 256 = 0 and -- DOS-like 303 (header.external_attributes and 1) = 1, 304 encrypted_2_x => (header.short_info.bit_flag and Zip.Headers.Encryption_Flag_Bit) /= 0, 305 root_node => p ); 306 -- Since the files are usually well ordered, the tree as inserted 307 -- is very unbalanced; we need to rebalance it from time to time 308 -- during loading, otherwise the insertion slows down dramatically 309 -- for zip files with plenty of files - converges to 310 -- O(total_entries ** 2)... 311 if i mod 256 = 0 then 312 Binary_tree_rebalancing.Rebalance(p); 313 end if; 314 end; 315 end loop; 316 Binary_tree_rebalancing.Rebalance(p); 317 info:= ( loaded => True, 318 case_sensitive => case_sensitive, 319 zip_file_name => new String'("This is a stream, no direct file!"), 320 zip_input_stream => from'Unchecked_Access, 321 dir_binary_tree => p, 322 total_entries => Integer(the_end.total_entries), 323 zip_file_comment => main_comment, 324 zip_archive_format => Zip_32 325 ); 326 exception 327 when Zip.Headers.bad_end => 328 Raise_Exception (Zip.Archive_corrupted'Identity, "Bad (or no) end-of-central-directory"); 329 when Zip.Headers.bad_central_header => 330 Raise_Exception (Zip.Archive_corrupted'Identity, "Bad central directory entry header"); 331 end Load; 332 333 ----------------------------------------------------------- 334 -- Load Zip_info from a file containing the .zip archive -- 335 ----------------------------------------------------------- 336 337 procedure Load( 338 info : out Zip_info; 339 from : in String; -- Zip file name 340 case_sensitive : in Boolean:= False; 341 duplicate_names : in Duplicate_name_policy:= error_on_duplicate 342 ) 343 is 344 use Zip_Streams; 345 MyStream : aliased File_Zipstream; 346 begin 347 Set_Name (MyStream, from); 348 begin 349 Open (MyStream, In_File); 350 exception 351 when others => 352 Raise_Exception (Zip_file_open_error'Identity, "Archive: [" & from & ']'); 353 end; 354 -- Call the stream version of Load(...) 355 Load( 356 info, 357 MyStream, 358 case_sensitive, 359 duplicate_names 360 ); 361 Close (MyStream); 362 Dispose(info.zip_file_name); 363 info.zip_file_name:= new String'(from); 364 info.zip_input_stream:= null; -- forget about the stream! 365 exception 366 when others => 367 if Is_Open(MyStream) then 368 Close(MyStream); 369 end if; 370 raise; 371 end Load; 372 373 function Is_loaded (info: in Zip_info) return Boolean is 374 begin 375 return info.loaded; 376 end Is_loaded; 377 378 function Zip_name( info: in Zip_info ) return String is 379 begin 380 if not info.loaded then 381 raise Forgot_to_load_zip_info; 382 end if; 383 return info.zip_file_name.all; 384 end Zip_name; 385 386 function Zip_comment( info: in Zip_info ) return String is 387 begin 388 if not info.loaded then 389 raise Forgot_to_load_zip_info; 390 end if; 391 return info.zip_file_comment.all; 392 end Zip_comment; 393 394 function Zip_stream( info: in Zip_info ) return Zip_Streams.Zipstream_Class_Access 395 is 396 begin 397 if not info.loaded then 398 raise Forgot_to_load_zip_info; 399 end if; 400 return info.zip_input_stream; 401 end Zip_stream; 402 403 function Entries( info: in Zip_info ) return Natural is 404 begin 405 return info.total_entries; 406 end Entries; 407 408 ------------ 409 -- Delete -- 410 ------------ 411 412 procedure Delete (info : in out Zip_info) is 413 414 procedure Delete( p: in out p_Dir_node ) is 415 begin 416 if p/=null then 417 Delete(p.left); 418 Delete(p.right); 419 Dispose(p); 420 p:= null; 421 end if; 422 end Delete; 423 424 begin 425 if not info.loaded then 426 raise Forgot_to_load_zip_info; 427 end if; 428 Delete( info.dir_binary_tree ); 429 Dispose( info.zip_file_name ); 430 Dispose( info.zip_file_comment ); 431 info.loaded:= False; -- <-- added 14-Jan-2002 432 end Delete; 433 434 -- Traverse a whole Zip_info directory in sorted order, giving the 435 -- name for each entry to an user-defined "Action" procedure. 436 437 generic 438 with procedure Action_private( dn: in out Dir_node ); 439 -- Dir_node is private: only known to us, contents subject to change 440 procedure Traverse_private( z: Zip_info ); 441 442 procedure Traverse_private( z: Zip_info ) is 443 444 procedure Traverse_tree( p: p_Dir_node ) is 445 begin 446 if p /= null then 447 Traverse_tree (p.left); 448 Action_private (p.all); 449 Traverse_tree (p.right); 450 end if; 451 end Traverse_tree; 452 453 begin 454 Traverse_tree (z.dir_binary_tree); 455 end Traverse_private; 456 457 ----------------------- 458 -- Public versions -- 459 ----------------------- 460 461 procedure Traverse( z: Zip_info ) is 462 procedure My_Action_private( dn: in out Dir_node ) is 463 pragma Inline(My_Action_private); 464 begin 465 Action(dn.file_name); 466 end My_Action_private; 467 procedure My_Traverse_private is new Traverse_private(My_Action_private); 468 begin 469 My_Traverse_private(z); 470 end Traverse; 471 472 procedure Traverse_Unicode( z: Zip_info ) is 473 procedure My_Action_private( dn: in out Dir_node ) is 474 pragma Inline(My_Action_private); 475 begin 476 Action(dn.file_name, dn.name_encoding); 477 end My_Action_private; 478 procedure My_Traverse_private is new Traverse_private(My_Action_private); 479 begin 480 My_Traverse_private(z); 481 end Traverse_Unicode; 482 483 procedure Traverse_verbose( z: Zip_info ) is 484 procedure My_Action_private( dn: in out Dir_node ) is 485 pragma Inline(My_Action_private); 486 begin 487 Action( 488 dn.file_name, 489 dn.file_index, 490 dn.comp_size, 491 dn.uncomp_size, 492 dn.crc_32, 493 dn.date_time, 494 dn.method, 495 dn.name_encoding, 496 dn.read_only, 497 dn.encrypted_2_x, 498 dn.user_code 499 ); 500 end My_Action_private; 501 procedure My_Traverse_private is new Traverse_private(My_Action_private); 502 begin 503 My_Traverse_private(z); 504 end Traverse_verbose; 505 506 procedure Tree_stat( 507 z : in Zip_info; 508 total : out Natural; 509 max_depth: out Natural; 510 avg_depth: out Float 511 ) 512 is 513 sum_depth: Natural:= 0; 514 515 procedure Traverse_tree( p: p_Dir_node; depth: Natural ) is 516 begin 517 if p /= null then 518 total:= total + 1; 519 if depth > max_depth then 520 max_depth:= depth; 521 end if; 522 sum_depth:= sum_depth + depth; 523 Traverse_tree (p.left, depth + 1); 524 Traverse_tree (p.right, depth + 1); 525 end if; 526 end Traverse_tree; 527 528 begin 529 total:= 0; 530 max_depth:= 0; 531 Traverse_tree(z.dir_binary_tree, 0); 532 if total = 0 then 533 avg_depth:= 0.0; 534 else 535 avg_depth:= Float(sum_depth) / Float(total); 536 end if; 537 end Tree_stat; 538 539 -- 13-May-2001: Find_first_offset 540 541 -- For an all-files unzipping of an appended (e.g. self-extracting) archive 542 -- (not beginning with ZIP contents), we cannot start with 543 -- index 1 in file. 544 -- But the offset of first entry in ZIP directory is not valid either, 545 -- as this excerpt of appnote.txt states: 546 547 -- " 4) The entries in the central directory may not necessarily 548 -- be in the same order that files appear in the zipfile. " 549 550 procedure Find_first_offset( 551 file : in out Zip_Streams.Root_Zipstream_Type'Class; 552 file_index : out Zip_Streams.ZS_Index_Type 553 ) 554 is 555 the_end : Zip.Headers.End_of_Central_Dir; 556 header : Zip.Headers.Central_File_Header; 557 min_offset: File_size_type; 558 use Zip_Streams; 559 begin 560 Zip.Headers.Load(file, the_end); 561 Set_Index( 562 file, 563 ZS_Index_Type (1 + the_end.central_dir_offset) + the_end.offset_shifting 564 ); 565 566 min_offset:= the_end.central_dir_offset; -- will be lowered if the archive is not empty. 567 568 if the_end.total_entries = 0 then 569 raise Archive_is_empty; 570 end if; 571 572 for i in 1..the_end.total_entries loop 573 Zip.Headers.Read_and_check(file, header ); 574 Set_Index( file, 575 Index( file ) + 576 ZS_Size_Type 577 ( header.short_info.filename_length + 578 header.short_info.extra_field_length + 579 header.comment_length 580 ) 581 ); 582 -- Now the whole i_th central directory entry is behind 583 584 if header.local_header_offset < min_offset then 585 min_offset:= header.local_header_offset; 586 end if; 587 end loop; 588 589 file_index:= Zip_Streams.ZS_Index_Type (1 + min_offset) + the_end.offset_shifting; 590 591 exception 592 when Zip.Headers.bad_end | Ada.IO_Exceptions.End_Error => 593 Raise_Exception (Zip.Archive_corrupted'Identity, "Bad (or no) end-of-central-directory"); 594 when Zip.Headers.bad_central_header => 595 Raise_Exception (Zip.Archive_corrupted'Identity, "Bad central directory entry header"); 596 end Find_first_offset; 597 598 -- Internal: find offset of a zipped file by reading sequentially the 599 -- central directory :-( 600 601 procedure Find_offset( 602 file : in out Zip_Streams.Root_Zipstream_Type'Class; 603 name : in String; 604 case_sensitive : in Boolean; 605 file_index : out Zip_Streams.ZS_Index_Type; 606 comp_size : out File_size_type; 607 uncomp_size : out File_size_type; 608 crc_32 : out Interfaces.Unsigned_32 609 ) 610 is 611 the_end: Zip.Headers.End_of_Central_Dir; 612 header : Zip.Headers.Central_File_Header; 613 use Zip_Streams; 614 begin 615 Zip.Headers.Load(file, the_end); 616 Set_Index(file, ZS_Index_Type(1 + the_end.central_dir_offset) + the_end.offset_shifting); 617 for i in 1..the_end.total_entries loop 618 Zip.Headers.Read_and_check(file, header); 619 declare 620 this_name: String(1..Natural(header.short_info.filename_length)); 621 begin 622 String'Read(file'Access, this_name); 623 Set_Index( file, 624 Index( file ) + 625 ZS_Size_Type( 626 header.short_info.extra_field_length + 627 header.comment_length 628 ) 629 ); 630 -- Now the whole i_th central directory entry is behind 631 if Normalize(this_name,case_sensitive) = 632 Normalize(name,case_sensitive) 633 then 634 -- Name found in central directory ! 635 file_index := Zip_Streams.ZS_Index_Type (1 + header.local_header_offset) + the_end.offset_shifting; 636 comp_size := File_size_type(header.short_info.dd.compressed_size); 637 uncomp_size:= File_size_type(header.short_info.dd.uncompressed_size); 638 crc_32 := header.short_info.dd.crc_32; 639 return; 640 end if; 641 end; 642 end loop; 643 Raise_Exception (File_name_not_found'Identity, "Entry: [" & name & ']'); 644 exception 645 when Zip.Headers.bad_end => 646 Raise_Exception (Zip.Archive_corrupted'Identity, "Bad (or no) end-of-central-directory"); 647 when Zip.Headers.bad_central_header => 648 Raise_Exception (Zip.Archive_corrupted'Identity, "Bad central directory entry header"); 649 end Find_offset; 650 651 -- Internal: find offset of a zipped file using the zip_info tree 8-) 652 653 procedure Find_offset( 654 info : in Zip_info; 655 name : in String; 656 name_encoding : out Zip_name_encoding; 657 file_index : out Zip_Streams.ZS_Index_Type; 658 comp_size : out File_size_type; 659 uncomp_size : out File_size_type; 660 crc_32 : out Interfaces.Unsigned_32 661 ) 662 is 663 aux: p_Dir_node:= info.dir_binary_tree; 664 up_name: constant String:= Normalize(name, info.case_sensitive); 665 begin 666 if not info.loaded then 667 raise Forgot_to_load_zip_info; 668 end if; 669 while aux /= null loop 670 if up_name > aux.dico_name then 671 aux:= aux.right; 672 elsif up_name < aux.dico_name then 673 aux:= aux.left; 674 else -- entry found ! 675 name_encoding := aux.name_encoding; 676 file_index := aux.file_index; 677 comp_size := aux.comp_size; 678 uncomp_size := aux.uncomp_size; 679 crc_32 := aux.crc_32; 680 return; 681 end if; 682 end loop; 683 Ada.Exceptions.Raise_Exception( 684 File_name_not_found'Identity, 685 "Archive: [" & info.zip_file_name.all & "], entry: [" & name & ']' 686 ); 687 end Find_offset; 688 689 procedure Find_offset_without_directory( 690 info : in Zip_info; 691 name : in String; 692 name_encoding : out Zip_name_encoding; 693 file_index : out Zip_Streams.ZS_Index_Type; 694 comp_size : out File_size_type; 695 uncomp_size : out File_size_type; 696 crc_32 : out Interfaces.Unsigned_32 697 ) 698 is 699 function Trash_dir( n: String ) return String is 700 idx: Integer:= n'First - 1; 701 begin 702 for i in n'Range loop 703 if n(i)= '/' or n(i)='\' then 704 idx:= i; 705 end if; 706 end loop; 707 -- idx points on the index just before the interesting part 708 return Normalize(n( idx+1 .. n'Last ), info.case_sensitive); 709 end Trash_dir; 710 711 simple_name: constant String:= Trash_dir(name); 712 713 Found: exception; 714 715 procedure Check_entry( 716 entry_name : String; -- 'name' is compressed entry's name 717 entry_index : Zip_Streams.ZS_Index_Type; 718 entry_comp_size : File_size_type; 719 entry_uncomp_size : File_size_type; 720 entry_crc_32 : Interfaces.Unsigned_32; 721 date_time : Time; 722 method : PKZip_method; 723 entry_name_encoding : Zip_name_encoding; 724 read_only : Boolean; 725 encrypted_2_x : Boolean; -- PKZip 2.x encryption 726 user_code : in out Integer 727 ) 728 is 729 pragma Unreferenced (date_time, method, read_only, encrypted_2_x, user_code); 730 begin 731 if Trash_dir(entry_name) = simple_name then 732 name_encoding := entry_name_encoding; 733 file_index := entry_index; 734 comp_size := entry_comp_size; 735 uncomp_size := entry_uncomp_size; 736 crc_32 := entry_crc_32; 737 raise Found; 738 end if; 739 end Check_entry; 740 -- 741 procedure Search is new Traverse_verbose(Check_entry); 742 -- 743 begin 744 begin 745 Search(info); 746 exception 747 when Found => 748 return; 749 end; 750 raise File_name_not_found; 751 end Find_offset_without_directory; 752 753 function Exists( 754 info : in Zip_info; 755 name : in String 756 ) 757 return Boolean 758 is 759 aux: p_Dir_node:= info.dir_binary_tree; 760 up_name: constant String:= Normalize(name, info.case_sensitive); 761 begin 762 if not info.loaded then 763 raise Forgot_to_load_zip_info; 764 end if; 765 while aux /= null loop 766 if up_name > aux.dico_name then 767 aux:= aux.right; 768 elsif up_name < aux.dico_name then 769 aux:= aux.left; 770 else -- entry found ! 771 return True; 772 end if; 773 end loop; 774 return False; 775 end Exists; 776 777 procedure Set_user_code( 778 info : in Zip_info; 779 name : in String; 780 code : in Integer 781 ) 782 is 783 aux: p_Dir_node:= info.dir_binary_tree; 784 up_name: constant String:= Normalize(name, info.case_sensitive); 785 begin 786 if not info.loaded then 787 raise Forgot_to_load_zip_info; 788 end if; 789 while aux /= null loop 790 if up_name > aux.dico_name then 791 aux:= aux.right; 792 elsif up_name < aux.dico_name then 793 aux:= aux.left; 794 else -- entry found ! 795 aux.user_code:= code; 796 return; 797 end if; 798 end loop; 799 Ada.Exceptions.Raise_Exception( 800 File_name_not_found'Identity, 801 "Archive: [" & info.zip_file_name.all & "], entry: [" & name & ']' 802 ); 803 end Set_user_code; 804 805 function User_code( 806 info : in Zip_info; 807 name : in String 808 ) 809 return Integer 810 is 811 aux: p_Dir_node:= info.dir_binary_tree; 812 up_name: constant String:= Normalize(name, info.case_sensitive); 813 begin 814 if not info.loaded then 815 raise Forgot_to_load_zip_info; 816 end if; 817 while aux /= null loop 818 if up_name > aux.dico_name then 819 aux:= aux.right; 820 elsif up_name < aux.dico_name then 821 aux:= aux.left; 822 else -- entry found ! 823 return aux.user_code; 824 end if; 825 end loop; 826 Ada.Exceptions.Raise_Exception( 827 File_name_not_found'Identity, 828 "Archive: [" & info.zip_file_name.all & "], entry: [" & name & ']' 829 ); 830 return 0; -- Fake, since exception has been raised just before. Removes an OA warning. 831 end User_code; 832 833 procedure Get_sizes( 834 info : in Zip_info; 835 name : in String; 836 comp_size : out File_size_type; 837 uncomp_size : out File_size_type 838 ) 839 is 840 dummy_file_index: Zip_Streams.ZS_Index_Type; 841 dummy_name_encoding: Zip_name_encoding; 842 dummy_crc_32: Interfaces.Unsigned_32; 843 begin 844 Find_offset( 845 info, name, dummy_name_encoding, dummy_file_index, 846 comp_size, uncomp_size, dummy_crc_32 847 ); 848 end Get_sizes; 849 850 -- Workaround for the severe xxx'Read xxx'Write performance 851 -- problems in the GNAT and ObjectAda compilers (as in 2009) 852 -- This is possible if and only if Byte = Stream_Element and 853 -- arrays types are both packed and aligned the same way. 854 -- 855 subtype Size_test_a is Byte_Buffer(1..19); 856 subtype Size_test_b is Ada.Streams.Stream_Element_Array(1..19); 857 workaround_possible: constant Boolean:= 858 Size_test_a'Size = Size_test_b'Size and 859 Size_test_a'Alignment = Size_test_b'Alignment; 860 861 -- BlockRead - general-purpose procedure (nothing really specific 862 -- to Zip / UnZip): reads either the whole buffer from a file, or 863 -- if the end of the file lays inbetween, a part of the buffer. 864 865 procedure BlockRead( 866 file : in Ada.Streams.Stream_IO.File_Type; 867 buffer : out Byte_Buffer; 868 actually_read: out Natural 869 ) 870 is 871 use Ada.Streams, Ada.Streams.Stream_IO; 872 SE_Buffer : Stream_Element_Array (1 .. buffer'Length); 873 for SE_Buffer'Address use buffer'Address; 874 pragma Import (Ada, SE_Buffer); 875 Last_Read : Stream_Element_Offset; 876 begin 877 if workaround_possible then 878 Read(Stream(file).all, SE_Buffer, Last_Read); 879 actually_read:= Natural(Last_Read); 880 else 881 if End_Of_File(file) then 882 actually_read:= 0; 883 else 884 actually_read:= 885 Integer'Min( buffer'Length, Integer(Size(file) - Index(file) + 1) ); 886 Byte_Buffer'Read( 887 Stream(file), 888 buffer(buffer'First .. buffer'First + actually_read - 1) 889 ); 890 end if; 891 end if; 892 end BlockRead; 893 894 procedure BlockRead( 895 stream : in out Zip_Streams.Root_Zipstream_Type'Class; 896 buffer : out Byte_Buffer; 897 actually_read: out Natural 898 ) 899 is 900 use Ada.Streams, Zip_Streams; 901 SE_Buffer : Stream_Element_Array (1 .. buffer'Length); 902 for SE_Buffer'Address use buffer'Address; 903 pragma Import (Ada, SE_Buffer); 904 Last_Read : Stream_Element_Offset; 905 begin 906 if workaround_possible then 907 Read(stream, SE_Buffer, Last_Read); 908 actually_read:= Natural(Last_Read); 909 else 910 if End_Of_Stream(stream) then 911 actually_read:= 0; 912 else 913 actually_read:= 914 Integer'Min( buffer'Length, Integer(Size(stream) - Index(stream) + 1) ); 915 Byte_Buffer'Read( 916 stream'Access, 917 buffer(buffer'First .. buffer'First + actually_read - 1) 918 ); 919 end if; 920 end if; 921 end BlockRead; 922 923 procedure BlockRead( 924 stream : in out Zip_Streams.Root_Zipstream_Type'Class; 925 buffer : out Byte_Buffer 926 ) 927 is 928 actually_read: Natural; 929 begin 930 BlockRead(stream, buffer, actually_read); 931 if actually_read < buffer'Length then 932 raise Ada.IO_Exceptions.End_Error; 933 end if; 934 end BlockRead; 935 936 procedure BlockWrite( 937 stream : in out Ada.Streams.Root_Stream_Type'Class; 938 buffer : in Byte_Buffer 939 ) 940 is 941 use Ada.Streams; 942 SE_Buffer : Stream_Element_Array (1 .. buffer'Length); 943 for SE_Buffer'Address use buffer'Address; 944 pragma Import (Ada, SE_Buffer); 945 begin 946 if workaround_possible then 947 Ada.Streams.Write(stream, SE_Buffer); 948 else 949 Byte_Buffer'Write(stream'Access, buffer); 950 -- ^This is 30x to 70x slower on GNAT 2009 ! 951 end if; 952 end BlockWrite; 953 954 function Image(m: PKZip_method) return String is 955 begin 956 case m is 957 when store => return "Store"; 958 when shrink => return "Shrink"; 959 when reduce_1 => return "Reduce 1"; 960 when reduce_2 => return "Reduce 2"; 961 when reduce_3 => return "Reduce 3"; 962 when reduce_4 => return "Reduce 4"; 963 when implode => return "Implode"; 964 when tokenize => return "Tokenize"; 965 when deflate => return "Deflate"; 966 when deflate_e => return "Deflate64"; 967 when bzip2 => return "BZip2"; 968 when lzma_meth => return "LZMA"; 969 when ppmd => return "PPMd"; 970 when unknown => return "(unknown)"; 971 end case; 972 end Image; 973 974 function Method_from_code(x: Natural) return PKZip_method is 975 -- An enumeration clause might be more elegant, but needs 976 -- curiously an Unchecked_Conversion... (RM 13.4) 977 begin 978 case x is 979 when compression_format_code.store => return store; 980 when compression_format_code.shrink => return shrink; 981 when compression_format_code.reduce => return reduce_1; 982 when compression_format_code.reduce + 1 => return reduce_2; 983 when compression_format_code.reduce + 2 => return reduce_3; 984 when compression_format_code.reduce + 3 => return reduce_4; 985 when compression_format_code.implode => return implode; 986 when compression_format_code.tokenize => return tokenize; 987 when compression_format_code.deflate => return deflate; 988 when compression_format_code.deflate_e => return deflate_e; 989 when compression_format_code.bzip2 => return bzip2; 990 when compression_format_code.lzma => return lzma_meth; 991 when compression_format_code.ppmd => return ppmd; 992 when others => return unknown; 993 end case; 994 end Method_from_code; 995 996 function Method_from_code(x: Interfaces.Unsigned_16) return PKZip_method is 997 begin 998 return Method_from_code(Natural(x)); 999 end Method_from_code; 1000 1001 -- Copy a chunk from a stream into another one, using a temporary buffer 1002 procedure Copy_chunk ( 1003 from : in out Zip_Streams.Root_Zipstream_Type'Class; 1004 into : in out Ada.Streams.Root_Stream_Type'Class; 1005 bytes : Natural; 1006 buffer_size: Positive:= 1024*1024; 1007 Feedback : Feedback_proc:= null 1008 ) 1009 is 1010 buf: Zip.Byte_Buffer(1..buffer_size); 1011 actually_read, remains: Natural; 1012 user_abort: Boolean:= False; 1013 begin 1014 remains:= bytes; 1015 while remains > 0 loop 1016 if Feedback /= null then 1017 Feedback( 1018 100 - Integer(100.0 * Float(remains) / Float(bytes)), 1019 False, 1020 user_abort 1021 ); 1022 -- !! do something if user_abort = True !! 1023 end if; 1024 Zip.BlockRead(from, buf(1..Integer'Min(remains, buf'Last)), actually_read); 1025 if actually_read = 0 then -- premature end, unexpected 1026 raise Zip.Archive_corrupted; 1027 end if; 1028 remains:= remains - actually_read; 1029 Zip.BlockWrite(into, buf(1..actually_read)); 1030 end loop; 1031 end Copy_chunk; 1032 1033 -- Copy a whole file into a stream, using a temporary buffer 1034 procedure Copy_file( 1035 file_name : String; 1036 into : in out Ada.Streams.Root_Stream_Type'Class; 1037 buffer_size: Positive:= 1024*1024 1038 ) 1039 is 1040 use Ada.Streams.Stream_IO; 1041 f: File_Type; 1042 buf: Zip.Byte_Buffer(1..buffer_size); 1043 actually_read: Natural; 1044 begin 1045 Open(f, In_File, file_name); 1046 loop 1047 Zip.BlockRead(f, buf, actually_read); 1048 exit when actually_read = 0; -- this is expected 1049 Zip.BlockWrite(into, buf(1..actually_read)); 1050 end loop; 1051 Close(f); 1052 end Copy_file; 1053 1054 -- This does the same as Ada 2005's Ada.Directories.Exists 1055 -- Just there as helper for Ada 95 only systems 1056 -- 1057 function Exists(name:String) return Boolean is 1058 use Ada.Text_IO, Ada.Strings.Fixed; 1059 f: File_Type; 1060 begin 1061 if Index(name, "*") > 0 then 1062 return False; 1063 end if; 1064 Open(f,In_File,name, Form => Ada.Strings.Unbounded.To_String (Zip_Streams.Form_For_IO_Open_and_Create)); 1065 Close(f); 1066 return True; 1067 exception 1068 when Name_Error => 1069 return False; -- The file cannot exist ! 1070 when Use_Error => 1071 return True; -- The file exist and is already opened ! 1072 end Exists; 1073 1074 procedure Put_Multi_Line( 1075 out_file : Ada.Text_IO.File_Type; 1076 text : String 1077 ) 1078 is 1079 last_char: Character:= ' '; 1080 c: Character; 1081 begin 1082 for i in text'Range loop 1083 c:= text(i); 1084 case c is 1085 when ASCII.CR => 1086 Ada.Text_IO.New_Line(out_file); 1087 when ASCII.LF => 1088 if last_char /= ASCII.CR then Ada.Text_IO.New_Line(out_file); end if; 1089 when others => 1090 Ada.Text_IO.Put(out_file, c); 1091 end case; 1092 last_char:= c; 1093 end loop; 1094 end Put_Multi_Line; 1095 1096 procedure Write_as_text( 1097 out_file : Ada.Text_IO.File_Type; 1098 buffer : Byte_Buffer; 1099 last_char: in out Character -- track line-ending characters across writes 1100 ) 1101 is 1102 c: Character; 1103 begin 1104 for i in buffer'Range loop 1105 c:= Character'Val(buffer(i)); 1106 case c is 1107 when ASCII.CR => 1108 Ada.Text_IO.New_Line(out_file); 1109 when ASCII.LF => 1110 if last_char /= ASCII.CR then Ada.Text_IO.New_Line(out_file); end if; 1111 when others => 1112 Ada.Text_IO.Put(out_file, c); 1113 end case; 1114 last_char:= c; 1115 end loop; 1116 end Write_as_text; 1117 1118 function Hexadecimal(x: Interfaces.Unsigned_32) return String 1119 is 1120 package MIO is new Ada.Text_IO.Modular_IO(Interfaces.Unsigned_32); 1121 str: String(1..12); 1122 use Ada.Strings.Fixed; 1123 begin 1124 MIO.Put(str, x, 16); 1125 return str(Index(str,"#")+1..11); 1126 end Hexadecimal; 1127 1128end Zip; 1129