Back to... Zip-Ada

Source file : zip.adb



   1  --  Legal licensing note:
   2  
   3  --  Copyright (c) 1999 .. 2025 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  
  27  with Zip.Headers;
  28  
  29  with Ada.Characters.Handling,
  30       Ada.Exceptions,
  31       Ada.Unchecked_Deallocation,
  32       Ada.IO_Exceptions,
  33       Ada.Strings.Fixed,
  34       Ada.Strings.Unbounded;
  35  
  36  package 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    is
 181      procedure Insert
 182        (dico_name        : String; -- UPPER if case-insensitive search
 183         file_name        : String;
 184         file_index       : Zip_Streams.ZS_Index_Type;
 185         comp_size,
 186         uncomp_size      : Zip_64_Data_Size_Type;
 187         crc_32           : Unsigned_32;
 188         date_time        : Time;
 189         method           : PKZip_method;
 190         name_encoding    : Zip_Name_Encoding;
 191         read_only        : Boolean;
 192         encrypted_2_x    : Boolean;
 193         root_node        : in out p_Dir_node)
 194      is
 195        procedure Insert_into_tree (node : in out p_Dir_node) is
 196        begin
 197          if node = null then
 198            node := new Dir_node'
 199              ((name_len          => file_name'Length,
 200                 left              => null,
 201                 right             => null,
 202                 dico_name         => dico_name,
 203                 file_name         => file_name,
 204                 file_index        => file_index,
 205                 comp_size         => comp_size,
 206                 uncomp_size       => uncomp_size,
 207                 crc_32            => crc_32,
 208                 date_time         => date_time,
 209                 method            => method,
 210                 name_encoding     => name_encoding,
 211                 read_only         => read_only,
 212                 encrypted_2_x     => encrypted_2_x,
 213                 user_code         => 0
 214                 )
 215              );
 216          elsif dico_name > node.dico_name then
 217            Insert_into_tree (node.right);
 218          elsif dico_name < node.dico_name then
 219            Insert_into_tree (node.left);
 220          else
 221            --  Here we have a case where the entry name already exists in the dictionary.
 222            case duplicate_names is
 223              when error_on_duplicate =>
 224                raise Duplicate_name with
 225                   "Same full entry name (in dictionary: " & dico_name &
 226                   ") appears twice in archive directory; " &
 227                   "procedure Load was called with strict name policy.";
 228              when admit_duplicates =>
 229                if file_index > node.file_index then
 230                  Insert_into_tree (node.right);
 231                elsif file_index < node.file_index then
 232                  Insert_into_tree (node.left);
 233                else
 234                  raise Duplicate_name with
 235                     "Archive directory corrupt: same full entry name (in dictionary: " &
 236                     dico_name & "), with same data position, appear twice.";
 237                end if;
 238            end case;
 239          end if;
 240        end Insert_into_tree;
 241        --
 242      begin
 243        Insert_into_tree (root_node);
 244      end Insert;
 245  
 246      the_end : Zip.Headers.End_of_Central_Dir;
 247      header  : Zip.Headers.Central_File_Header;
 248      p       : p_Dir_node := null;
 249      main_comment : p_String;
 250    begin  --  Load Zip_info
 251      if info.loaded then
 252        Delete (info);
 253      end if;
 254      Zip.Headers.Load (from, the_end);
 255      --  We take the opportunity to read the main comment, which is right
 256      --  after the end-of-central-directory block.
 257      main_comment := new String (1 .. Integer (the_end.main_comment_length));
 258      String'Read (from'Access, main_comment.all);
 259      --  Process central directory:
 260      Zip_Streams.Set_Index (
 261        from,
 262        Zip_Streams.ZS_Index_Type (1 + the_end.central_dir_offset) + the_end.offset_shifting
 263      );
 264  
 265      for i in 1 .. the_end.total_entries loop
 266        Zip.Headers.Read_and_Check (from, header);
 267        declare
 268          this_name : String (1 .. Natural (header.short_info.filename_length));
 269          mem : Zip_Streams.ZS_Index_Type;
 270          head_extra : Headers.Local_File_Header_Extension;
 271        begin
 272          String'Read (from'Access, this_name);
 273          mem := from.Index;
 274          if header.short_info.extra_field_length >= 4 then
 275            Headers.Read_and_Check (from, head_extra);
 276            Headers.Interpret
 277              (head_extra,
 278               header.short_info.dd.uncompressed_size,
 279               header.short_info.dd.compressed_size,
 280               header.local_header_offset);
 281          end if;
 282          --  Skip extra field and entry comment.
 283          from.Set_Index
 284            (mem +
 285             Zip_Streams.ZS_Size_Type
 286               (header.short_info.extra_field_length +
 287                header.comment_length));
 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      info.case_sensitive     := case_sensitive;
 319      info.zip_file_name      := new String'("This is a stream, no direct file!");
 320      info.zip_input_stream   := from'Unchecked_Access;
 321      info.dir_binary_tree    := p;
 322      info.total_entries      := Integer (the_end.total_entries);
 323      info.zip_file_comment   := main_comment;
 324      info.zip_archive_format := Zip_32;
 325    exception
 326      when E : Zip.Headers.bad_end =>
 327        raise Zip.Archive_corrupted
 328          with "Bad (or no) end-of-central-directory " & Ada.Exceptions.Exception_Message (E);
 329      when Zip.Headers.bad_central_header =>
 330        raise Zip.Archive_corrupted with "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    is
 343      my_stream : aliased Zip_Streams.File_Zipstream;
 344    begin
 345      my_stream.Set_Name (from);
 346      begin
 347        my_stream.Open (Zip_Streams.In_File);
 348      exception
 349        when others =>
 350          raise Archive_open_error with "Archive: [" & from & ']';
 351      end;
 352      --  Call the stream version of Load(...)
 353      Load (
 354        info,
 355        my_stream,
 356        case_sensitive,
 357        duplicate_names
 358      );
 359      my_stream.Close;
 360      Dispose (info.zip_file_name);
 361      info.zip_file_name := new String'(from);
 362      info.zip_input_stream := null; -- forget about the stream!
 363    exception
 364      when others =>
 365        if my_stream.Is_Open then
 366          my_stream.Close;
 367        end if;
 368        raise;
 369    end Load;
 370  
 371    function Is_loaded (info : in Zip_Info) return Boolean is
 372    begin
 373      return info.loaded;
 374    end Is_loaded;
 375  
 376    function Zip_Name (info : in Zip_Info) return String is
 377    begin
 378      if not info.loaded then
 379        raise Forgot_to_load_zip_info;
 380      end if;
 381      return info.zip_file_name.all;
 382    end Zip_Name;
 383  
 384    function Zip_Comment (info : in Zip_Info) return String is
 385    begin
 386      if not info.loaded then
 387        raise Forgot_to_load_zip_info;
 388      end if;
 389      return info.zip_file_comment.all;
 390    end Zip_Comment;
 391  
 392    function Zip_Stream (info : in Zip_Info) return Zip_Streams.Zipstream_Class_Access
 393    is
 394    begin
 395      if not info.loaded then
 396        raise Forgot_to_load_zip_info;
 397      end if;
 398      return info.zip_input_stream;
 399    end Zip_Stream;
 400  
 401    function Entries (info : in Zip_Info) return Natural is
 402    begin
 403      return info.total_entries;
 404    end Entries;
 405  
 406    ------------
 407    -- Delete --
 408    ------------
 409  
 410    procedure Delete (info : in out Zip_Info) is
 411  
 412      procedure Delete (p : in out p_Dir_node) is
 413      begin
 414        if p /= null then
 415           Delete (p.left);
 416           Delete (p.right);
 417           Dispose (p);
 418           p := null;
 419        end if;
 420      end Delete;
 421  
 422    begin
 423      Delete (info.dir_binary_tree);
 424      Dispose (info.zip_file_name);
 425      Dispose (info.zip_file_comment);
 426      info.loaded := False;  --  <-- added 14-Jan-2002
 427    end Delete;
 428  
 429    --  Traverse a whole Zip_info directory in sorted order, giving the
 430    --  name for each entry to an user-defined "Action" procedure.
 431  
 432    generic
 433      with procedure Action_private (dn : in out Dir_node);
 434      --  Dir_node is private: only known to us, contents subject to change
 435    procedure Traverse_private (z : Zip_Info);
 436  
 437    procedure Traverse_private (z : Zip_Info) is
 438  
 439      procedure Traverse_tree (p : p_Dir_node) is
 440      begin
 441        if p /= null then
 442          Traverse_tree (p.left);
 443          Action_private (p.all);
 444          Traverse_tree (p.right);
 445        end if;
 446      end Traverse_tree;
 447  
 448    begin
 449      Traverse_tree (z.dir_binary_tree);
 450    end Traverse_private;
 451  
 452    -----------------------
 453    --  Public versions  --
 454    -----------------------
 455  
 456    procedure Traverse (z : Zip_Info) is
 457      procedure My_Action_private (dn : in out Dir_node) is
 458      pragma Inline (My_Action_private);
 459      begin
 460        Action (dn.file_name);
 461      end My_Action_private;
 462      procedure My_Traverse_private is new Traverse_private (My_Action_private);
 463    begin
 464      My_Traverse_private (z);
 465    end Traverse;
 466  
 467    procedure Traverse_Unicode (z : Zip_Info) is
 468      procedure My_Action_private (dn : in out Dir_node) is
 469      pragma Inline (My_Action_private);
 470      begin
 471        Action (dn.file_name, dn.name_encoding);
 472      end My_Action_private;
 473      procedure My_Traverse_private is new Traverse_private (My_Action_private);
 474    begin
 475      My_Traverse_private (z);
 476    end Traverse_Unicode;
 477  
 478    procedure Traverse_verbose (z : Zip_Info) is
 479      procedure My_Action_private (dn : in out Dir_node) is
 480      pragma Inline (My_Action_private);
 481      begin
 482        Action (
 483          dn.file_name,
 484          dn.file_index,
 485          dn.comp_size,
 486          dn.uncomp_size,
 487          dn.crc_32,
 488          dn.date_time,
 489          dn.method,
 490          dn.name_encoding,
 491          dn.read_only,
 492          dn.encrypted_2_x,
 493          dn.user_code
 494        );
 495      end My_Action_private;
 496      procedure My_Traverse_private is new Traverse_private (My_Action_private);
 497    begin
 498      My_Traverse_private (z);
 499    end Traverse_verbose;
 500  
 501    procedure Tree_Stat
 502      (z         : in     Zip_Info;
 503       total     :    out Natural;
 504       max_depth :    out Natural;
 505       avg_depth :    out Float)
 506    is
 507      sum_depth : Natural := 0;
 508  
 509      procedure Traverse_tree (p : p_Dir_node; depth : Natural) is
 510      begin
 511        if p /= null then
 512          total := total + 1;
 513          if depth > max_depth then
 514            max_depth := depth;
 515          end if;
 516          sum_depth := sum_depth + depth;
 517          Traverse_tree (p.left, depth + 1);
 518          Traverse_tree (p.right, depth + 1);
 519        end if;
 520      end Traverse_tree;
 521  
 522    begin
 523      total := 0;
 524      max_depth := 0;
 525      Traverse_tree (z.dir_binary_tree, 0);
 526      if total = 0 then
 527        avg_depth := 0.0;
 528      else
 529        avg_depth := Float (sum_depth) / Float (total);
 530      end if;
 531    end Tree_Stat;
 532  
 533    --  13-May-2001: Find_first_offset
 534  
 535    --  For an all-files unzipping of an appended (e.g. self-extracting) archive
 536    --  (not beginning with ZIP contents), we cannot start with
 537    --  index 1 in file.
 538    --  But the offset of first entry in ZIP directory is not valid either,
 539    --  as this excerpt of appnote.txt states:
 540  
 541    --  "   4)  The entries in the central directory may not necessarily
 542    --          be in the same order that files appear in the zipfile.    "
 543  
 544    procedure Find_first_Offset (
 545      file           : in out Zip_Streams.Root_Zipstream_Type'Class;
 546      file_index     :    out Zip_Streams.ZS_Index_Type
 547    )
 548    is
 549      the_end    : Zip.Headers.End_of_Central_Dir;
 550      header     : Zip.Headers.Central_File_Header;
 551      min_offset : Zip_64_Data_Size_Type;
 552      mem        : Zip_Streams.ZS_Index_Type;
 553      head_extra : Headers.Local_File_Header_Extension;
 554    begin
 555      Zip.Headers.Load (file, the_end);
 556      file.Set_Index
 557        (Zip_Streams.ZS_Index_Type (1 + the_end.central_dir_offset) + the_end.offset_shifting);
 558  
 559      min_offset := the_end.central_dir_offset; -- will be lowered if the archive is not empty.
 560  
 561      if the_end.total_entries = 0 then
 562        raise Archive_is_empty;
 563      end if;
 564  
 565      for i in 1 .. the_end.total_entries loop
 566        Headers.Read_and_Check (file, header);
 567        file.Set_Index (file.Index + Zip_Streams.ZS_Size_Type (header.short_info.filename_length));
 568        mem := file.Index;
 569        if header.short_info.extra_field_length >= 4 then
 570          Headers.Read_and_Check (file, head_extra);
 571          Headers.Interpret
 572            (head_extra,
 573             header.short_info.dd.uncompressed_size,
 574             header.short_info.dd.compressed_size,
 575             header.local_header_offset);
 576        end if;
 577        file.Set_Index
 578          (mem +
 579           Zip_Streams.ZS_Size_Type
 580             (header.short_info.extra_field_length +
 581              header.comment_length));
 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 E : Zip.Headers.bad_end =>
 593        raise Zip.Archive_corrupted
 594          with "Bad (or no) end-of-central-directory " & Ada.Exceptions.Exception_Message (E);
 595      when Ada.IO_Exceptions.End_Error =>
 596        raise Zip.Archive_corrupted
 597          with "Bad (or no) end-of-central-directory (end of stream reached)";
 598      when Zip.Headers.bad_central_header =>
 599        raise Zip.Archive_corrupted with "Bad central directory entry header";
 600    end Find_first_Offset;
 601  
 602    --  Internal: find offset of a zipped file by reading sequentially the
 603    --  central directory :-(
 604  
 605    procedure Find_Offset (
 606      file           : in out Zip_Streams.Root_Zipstream_Type'Class;
 607      name           : in     String;
 608      case_sensitive : in     Boolean;
 609      file_index     :    out Zip_Streams.ZS_Index_Type;
 610      comp_size      :    out Zip_64_Data_Size_Type;
 611      uncomp_size    :    out Zip_64_Data_Size_Type;
 612      crc_32         :    out Interfaces.Unsigned_32
 613    )
 614    is
 615      the_end : Zip.Headers.End_of_Central_Dir;
 616      header  : Zip.Headers.Central_File_Header;
 617      mem : Zip_Streams.ZS_Index_Type;
 618      head_extra : Headers.Local_File_Header_Extension;
 619    begin
 620      Zip.Headers.Load (file, the_end);
 621      file.Set_Index
 622        (Zip_Streams.ZS_Index_Type (1 + the_end.central_dir_offset) + the_end.offset_shifting);
 623      for i in 1 .. the_end.total_entries loop
 624        Zip.Headers.Read_and_Check (file, header);
 625        declare
 626          this_name : String (1 .. Natural (header.short_info.filename_length));
 627        begin
 628          String'Read (file'Access, this_name);
 629          mem := file.Index;
 630          if header.short_info.extra_field_length >= 4 then
 631            Headers.Read_and_Check (file, head_extra);
 632            Headers.Interpret
 633              (head_extra,
 634               header.short_info.dd.uncompressed_size,
 635               header.short_info.dd.compressed_size,
 636               header.local_header_offset);
 637            end if;
 638          file.Set_Index
 639            (mem +
 640              Zip_Streams.ZS_Size_Type
 641                (header.short_info.extra_field_length +
 642                 header.comment_length));
 643          --  Now the whole i_th central directory entry is behind
 644          if Normalize (this_name, case_sensitive) =
 645             Normalize (name, case_sensitive)
 646          then
 647            --  Name found in central directory !
 648            file_index  := Zip_Streams.ZS_Index_Type (1 + header.local_header_offset) + the_end.offset_shifting;
 649            comp_size   := Zip_64_Data_Size_Type (header.short_info.dd.compressed_size);
 650            uncomp_size := Zip_64_Data_Size_Type (header.short_info.dd.uncompressed_size);
 651            crc_32      := header.short_info.dd.crc_32;
 652            return;
 653          end if;
 654        end;
 655      end loop;
 656      raise Entry_name_not_found with "Entry: [" & name & ']';
 657    exception
 658      when Zip.Headers.bad_end =>
 659        raise Zip.Archive_corrupted with "Bad (or no) end-of-central-directory";
 660      when Zip.Headers.bad_central_header =>
 661        raise Zip.Archive_corrupted with "Bad central directory entry header";
 662    end Find_Offset;
 663  
 664    --  Internal: find offset of a zipped file using the zip_info tree 8-)
 665  
 666    procedure Find_Offset
 667      (info           : in     Zip_Info;
 668       name           : in     String;
 669       name_encoding  :    out Zip_Name_Encoding;
 670       file_index     :    out Zip_Streams.ZS_Index_Type;
 671       comp_size      :    out Zip_64_Data_Size_Type;
 672       uncomp_size    :    out Zip_64_Data_Size_Type;
 673       crc_32         :    out Interfaces.Unsigned_32)
 674    is
 675      aux : p_Dir_node := info.dir_binary_tree;
 676      up_name : constant String := Normalize (name, info.case_sensitive);
 677    begin
 678      if not info.loaded then
 679        raise Forgot_to_load_zip_info;
 680      end if;
 681      while aux /= null loop
 682        if up_name > aux.dico_name then
 683          aux := aux.right;
 684        elsif up_name < aux.dico_name then
 685          aux := aux.left;
 686        else  -- entry found !
 687          name_encoding := aux.name_encoding;
 688          file_index    := aux.file_index;
 689          comp_size     := aux.comp_size;
 690          uncomp_size   := aux.uncomp_size;
 691          crc_32        := aux.crc_32;
 692          return;
 693        end if;
 694      end loop;
 695      raise Entry_name_not_found with "Archive: [" & info.zip_file_name.all & "], entry: [" & name & ']';
 696    end Find_Offset;
 697  
 698    procedure Find_Offset_without_Directory
 699      (info           : in     Zip_Info;
 700       name           : in     String;
 701       name_encoding  :    out Zip_Name_Encoding;
 702       file_index     :    out Zip_Streams.ZS_Index_Type;
 703       comp_size      :    out Zip_64_Data_Size_Type;
 704       uncomp_size    :    out Zip_64_Data_Size_Type;
 705       crc_32         :    out Interfaces.Unsigned_32)
 706    is
 707      function Trash_dir (n : String) return String is
 708        idx : Integer := n'First - 1;
 709      begin
 710        for i in n'Range loop
 711          if n (i) in '/' | '\' then
 712            idx := i;
 713          end if;
 714        end loop;
 715        --  idx points on the index just before the interesting part
 716        return Normalize (n (idx + 1 .. n'Last), info.case_sensitive);
 717      end Trash_dir;
 718  
 719      simple_name : constant String := Trash_dir (name);
 720  
 721      Found : exception;
 722  
 723      procedure Check_entry (
 724        entry_name          : String; -- 'name' is compressed entry's name
 725        entry_index         : Zip_Streams.ZS_Index_Type;
 726        entry_comp_size     : Zip_64_Data_Size_Type;
 727        entry_uncomp_size   : Zip_64_Data_Size_Type;
 728        entry_crc_32        : Interfaces.Unsigned_32;
 729        date_time           : Time;
 730        method              : PKZip_method;
 731        entry_name_encoding : Zip_Name_Encoding;
 732        read_only           : Boolean;
 733        encrypted_2_x       : Boolean; -- PKZip 2.x encryption
 734        entry_user_code     : in out Integer
 735      )
 736      is
 737      pragma Unreferenced (date_time, method, read_only, encrypted_2_x, entry_user_code);
 738      begin
 739        if Trash_dir (entry_name) = simple_name then
 740          name_encoding := entry_name_encoding;
 741          file_index    := entry_index;
 742          comp_size     := entry_comp_size;
 743          uncomp_size   := entry_uncomp_size;
 744          crc_32        := entry_crc_32;
 745          raise Found;
 746        end if;
 747      end Check_entry;
 748      --
 749      procedure Search is new Traverse_verbose (Check_entry);
 750      --
 751    begin
 752      begin
 753        Search (info);
 754      exception
 755        when Found =>
 756          return;
 757      end;
 758      raise Entry_name_not_found with "Archive: [" & info.zip_file_name.all & "], entry: [" & name & ']';
 759    end Find_Offset_without_Directory;
 760  
 761    function Exists (info : Zip_Info; name : String) return Boolean
 762    is
 763      aux : p_Dir_node := info.dir_binary_tree;
 764      up_name : constant String := Normalize (name, info.case_sensitive);
 765    begin
 766      if not info.loaded then
 767        raise Forgot_to_load_zip_info;
 768      end if;
 769      while aux /= null loop
 770        if up_name > aux.dico_name then
 771          aux := aux.right;
 772        elsif up_name < aux.dico_name then
 773          aux := aux.left;
 774        else  --  entry found !
 775          return True;
 776        end if;
 777      end loop;
 778      return False;
 779    end Exists;
 780  
 781    procedure Set_User_Code (info : Zip_Info; name : String; code : Integer) is
 782      aux : p_Dir_node := info.dir_binary_tree;
 783      up_name : constant String := Normalize (name, info.case_sensitive);
 784    begin
 785      if not info.loaded then
 786        raise Forgot_to_load_zip_info;
 787      end if;
 788      while aux /= null loop
 789        if up_name > aux.dico_name then
 790          aux := aux.right;
 791        elsif up_name < aux.dico_name then
 792          aux := aux.left;
 793        else  --  entry found !
 794          aux.user_code := code;
 795          return;
 796        end if;
 797      end loop;
 798      raise Entry_name_not_found with "Archive: [" & info.zip_file_name.all & "], entry: [" & name & ']';
 799    end Set_User_Code;
 800  
 801    function User_Code (info : Zip_Info; name : String) return Integer
 802    is
 803      aux : p_Dir_node := info.dir_binary_tree;
 804      up_name : constant String := Normalize (name, info.case_sensitive);
 805    begin
 806      if not info.loaded then
 807        raise Forgot_to_load_zip_info;
 808      end if;
 809      while aux /= null loop
 810        if up_name > aux.dico_name then
 811          aux := aux.right;
 812        elsif up_name < aux.dico_name then
 813          aux := aux.left;
 814        else  --  entry found !
 815          return aux.user_code;
 816        end if;
 817      end loop;
 818      raise Entry_name_not_found with "Archive: [" & info.zip_file_name.all & "], entry: [" & name & ']';
 819      return 0;  --  Fake, since exception has been raised just before. Removes an OA warning.
 820    end User_Code;
 821  
 822    procedure Get_Sizes
 823      (info           : in     Zip_Info;
 824       name           : in     String;
 825       comp_size      :    out Zip_64_Data_Size_Type;
 826       uncomp_size    :    out Zip_64_Data_Size_Type)
 827    is
 828      dummy_file_index : Zip_Streams.ZS_Index_Type;
 829      dummy_name_encoding : Zip_Name_Encoding;
 830      dummy_crc_32 : Interfaces.Unsigned_32;
 831    begin
 832      Find_Offset
 833        (info, name, dummy_name_encoding, dummy_file_index,
 834         comp_size, uncomp_size, dummy_crc_32);
 835    end Get_Sizes;
 836  
 837    --  Workaround for the severe xxx'Read xxx'Write performance
 838    --  problems in the GNAT and ObjectAda compilers (as in 2009)
 839    --  This is possible if and only if Byte = Stream_Element and
 840    --  arrays types are both packed and aligned the same way.
 841    --
 842    subtype Size_test_a is Byte_Buffer (1 .. 19);
 843    subtype Size_test_b is Ada.Streams.Stream_Element_Array (1 .. 19);
 844    workaround_possible : constant Boolean :=
 845      Size_test_a'Size = Size_test_b'Size and
 846      Size_test_a'Alignment = Size_test_b'Alignment;
 847  
 848    --  Block_Read - general-purpose procedure (nothing really specific
 849    --  to Zip / UnZip): reads either the whole buffer from a file, or
 850    --  if the end of the file lays inbetween, a part of the buffer.
 851  
 852    procedure Block_Read
 853      (file          : in     Ada.Streams.Stream_IO.File_Type;
 854       buffer        :    out Byte_Buffer;
 855       actually_read :    out Natural)
 856    is
 857      use Ada.Streams, Ada.Streams.Stream_IO;
 858      SE_Buffer   : Stream_Element_Array (1 .. buffer'Length);
 859      for SE_Buffer'Address use buffer'Address;
 860      pragma Import (Ada, SE_Buffer);
 861      Last_Read   : Stream_Element_Offset;
 862    begin
 863      if workaround_possible then
 864        Read (Stream (file).all, SE_Buffer, Last_Read);
 865        actually_read := Natural (Last_Read);
 866      else
 867        if End_Of_File (file) then
 868          actually_read := 0;
 869        else
 870          actually_read :=
 871            Integer'Min (buffer'Length, Integer (Size (file) - Index (file) + 1));
 872          Byte_Buffer'Read (
 873            Stream (file),
 874            buffer (buffer'First .. buffer'First + actually_read - 1)
 875          );
 876        end if;
 877      end if;
 878    end Block_Read;
 879  
 880    procedure Block_Read
 881      (stream        : in out Zip_Streams.Root_Zipstream_Type'Class;
 882       buffer        :    out Byte_Buffer;
 883       actually_read :    out Natural)
 884    is
 885      use Ada.Streams;
 886      SE_Buffer   : Stream_Element_Array (1 .. buffer'Length);
 887      for SE_Buffer'Address use buffer'Address;
 888      pragma Import (Ada, SE_Buffer);
 889      Last_Read   : Stream_Element_Offset;
 890    begin
 891      if workaround_possible then
 892        stream.Read (SE_Buffer, Last_Read);
 893        actually_read := Natural (Last_Read);
 894      else
 895        if stream.End_Of_Stream then
 896          actually_read := 0;
 897        else
 898          actually_read :=
 899            Integer'Min (buffer'Length, Integer (stream.Size - stream.Index + 1));
 900          Byte_Buffer'Read (
 901            stream'Access,
 902            buffer (buffer'First .. buffer'First + actually_read - 1)
 903          );
 904        end if;
 905      end if;
 906    end Block_Read;
 907  
 908    procedure Block_Read
 909      (stream : in out Zip_Streams.Root_Zipstream_Type'Class;
 910       buffer :    out Byte_Buffer)
 911    is
 912      actually_read : Natural;
 913    begin
 914      Block_Read (stream, buffer, actually_read);
 915      if actually_read < buffer'Length then
 916        raise Ada.IO_Exceptions.End_Error;
 917      end if;
 918    end Block_Read;
 919  
 920    procedure Block_Write
 921      (stream : in out Ada.Streams.Root_Stream_Type'Class;
 922       buffer : in     Byte_Buffer)
 923    is
 924      use Ada.Streams;
 925      SE_Buffer   : Stream_Element_Array (1 .. buffer'Length);
 926      for SE_Buffer'Address use buffer'Address;
 927      pragma Import (Ada, SE_Buffer);
 928    begin
 929      if workaround_possible then
 930        Ada.Streams.Write (stream, SE_Buffer);
 931      else
 932        Byte_Buffer'Write (stream'Access, buffer);
 933        --  ^This is 30x to 70x slower on GNAT 2009 !
 934      end if;
 935    end Block_Write;
 936  
 937    function Image (m : PKZip_method) return String is
 938    begin
 939      case m is
 940        when store       => return "Store";
 941        when shrink      => return "Shrink";
 942        when reduce_1    => return "Reduce 1";
 943        when reduce_2    => return "Reduce 2";
 944        when reduce_3    => return "Reduce 3";
 945        when reduce_4    => return "Reduce 4";
 946        when implode     => return "Implode";
 947        when tokenize    => return "Tokenize";
 948        when deflate     => return "Deflate";
 949        when deflate_e   => return "Deflate64";
 950        when bzip2_meth       => return "BZip2";
 951        when lzma_meth   => return "LZMA";
 952        when zstandard   => return "Zstandard";
 953        when mp3_recomp  => return "MP3 recompression";
 954        when xz_recomp   => return "XZ recompression";
 955        when jpeg_recomp => return "JPEG recompression";
 956        when wavpack     => return "WAVE recompression";
 957        when ppmd        => return "PPMd";
 958        when unknown     => return "(unknown)";
 959      end case;
 960    end Image;
 961  
 962    function Method_from_Code (x : Natural) return PKZip_method is
 963      --  An enumeration clause might be more elegant instead of this function,
 964      --  but would need curiously an Unchecked_Conversion... (RM 13.4)
 965      use Compression_format_code;
 966    begin
 967      case x is
 968        when store_code      => return store;
 969        when shrink_code     => return shrink;
 970        when reduce_code     => return reduce_1;
 971        when reduce_code + 1 => return reduce_2;
 972        when reduce_code + 2 => return reduce_3;
 973        when reduce_code + 3 => return reduce_4;
 974        when implode_code    => return implode;
 975        when tokenize_code   => return tokenize;
 976        when deflate_code    => return deflate;
 977        when deflate_e_code  => return deflate_e;
 978        when bzip2_code      => return bzip2_meth;
 979        when lzma_code       => return lzma_meth;
 980        when zstandard_code  => return zstandard;
 981        when mp3_code        => return mp3_recomp;
 982        when xz_code         => return xz_recomp;
 983        when jpeg_code       => return jpeg_recomp;
 984        when wavpack_code    => return wavpack;
 985        when ppmd_code       => return ppmd;
 986        when others          => return unknown;
 987      end case;
 988    end Method_from_Code;
 989  
 990    function Method_from_Code (x : Interfaces.Unsigned_16) return PKZip_method is
 991    begin
 992      return Method_from_Code (Natural (x));
 993    end Method_from_Code;
 994  
 995    --  Copy a chunk from a stream into another one, using a temporary buffer
 996    procedure Copy_Chunk
 997      (from        : in out Zip_Streams.Root_Zipstream_Type'Class;
 998       into        : in out Ada.Streams.Root_Stream_Type'Class;
 999       bytes       : Natural;
1000       buffer_size : Positive := 1024 * 1024;
1001       Feedback    : Feedback_Proc := null)
1002    is
1003      buf : Zip.Byte_Buffer (1 .. buffer_size);
1004      actually_read, remains : Natural;
1005      user_abort : Boolean := False;
1006    begin
1007      remains := bytes;
1008      while remains > 0 loop
1009        if Feedback /= null then
1010          Feedback (
1011            100 - Integer (100.0 * Float (remains) / Float (bytes)),
1012            False,
1013            user_abort
1014          );
1015          --  !! do something if user_abort = True !!
1016        end if;
1017        Zip.Block_Read (from, buf (1 .. Integer'Min (remains, buf'Last)), actually_read);
1018        if actually_read = 0 then -- premature end, unexpected
1019          raise Zip.Archive_corrupted;
1020        end if;
1021        remains := remains - actually_read;
1022        Zip.Block_Write (into, buf (1 .. actually_read));
1023      end loop;
1024    end Copy_Chunk;
1025  
1026    --  Copy a whole file into a stream, using a temporary buffer
1027    procedure Copy_File
1028      (file_name   : String;
1029       into        : in out Ada.Streams.Root_Stream_Type'Class;
1030       buffer_size : Positive := 1024 * 1024)
1031    is
1032      use Ada.Streams.Stream_IO;
1033      f : File_Type;
1034      buf : Zip.Byte_Buffer (1 .. buffer_size);
1035      actually_read : Natural;
1036    begin
1037      Open (f, In_File, file_name);
1038      loop
1039        Zip.Block_Read (f, buf, actually_read);
1040        exit when actually_read = 0; -- this is expected
1041        Zip.Block_Write (into, buf (1 .. actually_read));
1042      end loop;
1043      Close (f);
1044    end Copy_File;
1045  
1046    --  This does the same as Ada 2005's Ada.Directories.Exists
1047    --  Just there as helper for Ada 95 only systems
1048    --
1049    function Exists (file_name : String) return Boolean is
1050      use Ada.Text_IO, Ada.Strings.Fixed;
1051      f : File_Type;
1052    begin
1053      if Index (file_name, "*") > 0 then
1054        return False;
1055      end if;
1056      Open (f, In_File, file_name, Form => Ada.Strings.Unbounded.To_String (Zip_Streams.Form_For_IO_Open_and_Create));
1057      Close (f);
1058      return True;
1059    exception
1060      when Name_Error =>
1061        return False;  --  The file cannot exist !
1062      when Use_Error =>
1063        return True;   --  The file exists and is already opened !
1064    end Exists;
1065  
1066    procedure Put_Multi_Line
1067      (out_file :        Ada.Text_IO.File_Type;
1068       text     :        String)
1069    is
1070      last_char : Character := ' ';
1071      c : Character;
1072    begin
1073      for i in text'Range loop
1074        c := text (i);
1075        case c is
1076          when ASCII.CR =>
1077            Ada.Text_IO.New_Line (out_file);
1078          when ASCII.LF =>
1079            if last_char /= ASCII.CR then Ada.Text_IO.New_Line (out_file); end if;
1080          when others =>
1081            Ada.Text_IO.Put (out_file, c);
1082        end case;
1083        last_char := c;
1084      end loop;
1085    end Put_Multi_Line;
1086  
1087    procedure Write_as_Text
1088      (out_file  :        Ada.Text_IO.File_Type;
1089       buffer    :        Byte_Buffer;
1090       last_char : in out Character)  --  track line-ending characters across writes
1091    is
1092      c : Character;
1093    begin
1094      for i in buffer'Range loop
1095        c := Character'Val (buffer (i));
1096        case c is
1097          when ASCII.CR =>
1098            Ada.Text_IO.New_Line (out_file);
1099          when ASCII.LF =>
1100            if last_char /= ASCII.CR then Ada.Text_IO.New_Line (out_file); end if;
1101          when others =>
1102            Ada.Text_IO.Put (out_file, c);
1103        end case;
1104        last_char := c;
1105      end loop;
1106    end Write_as_Text;
1107  
1108    function Hexadecimal (x : Interfaces.Unsigned_32) return String
1109    is
1110      package MIO is new Ada.Text_IO.Modular_IO (Interfaces.Unsigned_32);
1111      str : String (1 .. 12);
1112      use Ada.Strings.Fixed;
1113    begin
1114      MIO.Put (str, x, 16);
1115      return str (Index (str, "#") + 1 .. 11);
1116    end Hexadecimal;
1117  
1118    overriding procedure Adjust (info : in out Zip_Info) is
1119  
1120      function Tree_Clone (p : in p_Dir_node) return p_Dir_node is
1121        q : p_Dir_node;
1122      begin
1123        if p = null then
1124          return null;
1125        else
1126          q := new Dir_node'(p.all);
1127          q.left  := Tree_Clone (p.left);
1128          q.right := Tree_Clone (p.right);
1129          return q;
1130        end if;
1131      end Tree_Clone;
1132  
1133    begin
1134      info.dir_binary_tree  := Tree_Clone (info.dir_binary_tree);
1135      info.zip_file_name    := new String'(info.zip_file_name.all);
1136      info.zip_file_comment := new String'(info.zip_file_comment.all);
1137    end Adjust;
1138  
1139    overriding procedure Finalize (info : in out Zip_Info) is
1140    begin
1141      Delete (info);
1142    end Finalize;
1143  
1144  end Zip;

Web view of Ada source code generated by GNATHTML, project: ALI_Parse version 1.0.
Zip-Ada: Ada library for zip archive files (.zip). Ada programming.
Some news about Zip-Ada and other Ada projects on Gautier's blog.