Back to... Zip-Ada

Source file : unzip-streams.adb



   1  --  Legal licensing note:
   2  
   3  --  Copyright (c) 1999 .. 2024 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 on the site
  25  --  http://www.opensource.org/licenses/mit-license.php
  26  
  27  with Zip.Headers, UnZip.Decompress;
  28  
  29  with Ada.Strings.Unbounded,
  30       Ada.Unchecked_Deallocation;
  31  
  32  with Interfaces;
  33  
  34  package body UnZip.Streams is
  35  
  36     procedure Dispose is new
  37       Ada.Unchecked_Deallocation (String, p_String);
  38  
  39     procedure Dispose is new
  40       Ada.Unchecked_Deallocation (Ada.Streams.Stream_Element_Array,
  41                                   p_Stream_Element_Array);
  42  
  43     procedure Dispose is new
  44       Ada.Unchecked_Deallocation (UnZip_Stream_Type,
  45                                   Zipped_File_Type);
  46  
  47     use Interfaces;
  48  
  49    --------------------------------------------------
  50    -- *The* internal 1-file unzipping procedure.   --
  51    -- Input must be _open_ and won't be _closed_ ! --
  52    --------------------------------------------------
  53  
  54    procedure UnZipFile (
  55      zip_stream      : in out Zip_Streams.Root_Zipstream_Type'Class;
  56      header_index    : in out Zip_Streams.ZS_Index_Type;
  57      mem_ptr         :    out p_Stream_Element_Array;
  58      out_stream_ptr  :        p_Stream;
  59      --  if not null, extract to out_stream_ptr, not to memory
  60      password        : in out Ada.Strings.Unbounded.Unbounded_String;
  61      hint_comp_size  : in     Zip.Zip_64_Data_Size_Type; -- Added 2007 for .ODS files
  62      hint_crc_32     : in     Unsigned_32;    -- Added 2012 for decryption
  63      cat_uncomp_size : in     Zip.Zip_64_Data_Size_Type
  64    )
  65    is
  66      work_index : Zip_Streams.ZS_Index_Type := header_index;
  67      local_header : Zip.Headers.Local_File_Header;
  68      data_descriptor_after_data : Boolean;
  69      encrypted : Boolean;
  70      method : Zip.PKZip_method;
  71      use type Zip.PKZip_method;
  72      mode : Write_Mode_Type;
  73    begin
  74      begin
  75        Zip_Streams.Set_Index (zip_stream, header_index);
  76        Zip.Headers.Read_and_Check (zip_stream, local_header);
  77      exception
  78        when Zip.Headers.bad_local_header =>
  79          raise Zip.Archive_corrupted with "Bad local header";
  80        when others =>
  81          raise Zip.Archive_corrupted;
  82      end;
  83  
  84      method := Zip.Method_from_Code (local_header.zip_type);
  85      if method = Zip.unknown then
  86        raise Unsupported_method;
  87      end if;
  88  
  89      --  Calculate offset of data
  90  
  91      work_index :=
  92        work_index +
  93        Zip_Streams.ZS_Size_Type
  94          (local_header.filename_length    +
  95           local_header.extra_field_length +
  96           Zip.Headers.local_header_length);
  97  
  98      --
  99      --  Zip64 extension.
 100      --
 101      if local_header.extra_field_length >= 4 then
 102        declare
 103          mem                    : constant Zip_Streams.ZS_Index_Type := Zip_Streams.Index (zip_stream);
 104          local_header_extension : Zip.Headers.Local_File_Header_Extension;
 105          dummy_offset           : Unsigned_64 := 0;
 106          --  ^ Initialized for avoiding a random value being by mistake 16#FFFF_FFFF#
 107        begin
 108          Zip_Streams.Set_Index (zip_stream, mem + Zip_Streams.ZS_Index_Type (local_header.filename_length));
 109          Zip.Headers.Read_and_Check (zip_stream, local_header_extension);
 110          Zip_Streams.Set_Index (zip_stream, mem);
 111          Zip.Headers.Interpret
 112            (local_header_extension,
 113             local_header.dd.uncompressed_size,
 114             local_header.dd.compressed_size,
 115             dummy_offset);
 116        end;
 117      end if;
 118  
 119      data_descriptor_after_data := (local_header.bit_flag and 8) /= 0;
 120  
 121      if data_descriptor_after_data then
 122        --  Sizes and crc are after the data
 123        local_header.dd.crc_32            := hint_crc_32;
 124        local_header.dd.uncompressed_size := cat_uncomp_size;
 125        local_header.dd.compressed_size   := hint_comp_size;
 126      else
 127        --  Sizes and crc are before the data
 128        if cat_uncomp_size /= local_header.dd.uncompressed_size then
 129          raise Uncompressed_Size_Error
 130            with "Uncompressed size mismatch: in catalogue:" & cat_uncomp_size'Image &
 131                 "; in local header:" & local_header.dd.uncompressed_size'Image;
 132        end if;
 133      end if;
 134  
 135      encrypted := (local_header.bit_flag and Zip.Headers.Encryption_Flag_Bit) /= 0;
 136  
 137      begin
 138        Zip_Streams.Set_Index (zip_stream, work_index);  --  eventually skips the file name
 139      exception
 140        when others =>
 141          raise Zip.Archive_corrupted with
 142            "End of stream reached (location: between local header and archived data)";
 143      end;
 144  
 145      if out_stream_ptr = null then
 146        mode := write_to_memory;
 147      else
 148        mode := write_to_stream;
 149      end if;
 150      --  Unzip correct type
 151      UnZip.Decompress.Decompress_Data (
 152        zip_file                   => zip_stream,
 153        format                     => method,
 154        write_mode                 => mode,
 155        output_file_name           => "",
 156        output_memory_access       => mem_ptr,
 157        output_stream_access       => out_stream_ptr,
 158        feedback                   => null,
 159        explode_literal_tree       => (local_header.bit_flag and 4) /= 0,
 160        explode_slide_8KB_LZMA_EOS => (local_header.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0,
 161        data_descriptor_after_data => data_descriptor_after_data,
 162        is_encrypted               => encrypted,
 163        password                   => password,
 164        get_new_password           => null,
 165        hint                       => local_header
 166      );
 167  
 168      --  Set the offset on the next zipped file
 169      header_index := header_index +
 170        Zip_Streams.ZS_Size_Type (
 171                local_header.filename_length    +
 172                local_header.extra_field_length +
 173                Zip.Headers.local_header_length
 174        ) +
 175        Zip_Streams.ZS_Size_Type (
 176          local_header.dd.compressed_size
 177        );
 178  
 179      if data_descriptor_after_data then
 180        header_index := header_index +
 181          Zip_Streams.ZS_Size_Type (Zip.Headers.data_descriptor_length);
 182      end if;
 183  
 184    end UnZipFile;
 185  
 186    procedure S_Extract
 187      (from             : in     Zip.Zip_Info;
 188       zip_stream       : in out Zip_Streams.Root_Zipstream_Type'Class;
 189       what             : in     String;
 190       password         : in     String;
 191       mem_ptr          :    out p_Stream_Element_Array;
 192       out_stream_ptr   : in     p_Stream;
 193       Ignore_Directory : in     Boolean)
 194    is
 195      header_index : Zip_Streams.ZS_Index_Type;
 196      comp_size    : Zip.Zip_64_Data_Size_Type;
 197      uncomp_size  : Zip.Zip_64_Data_Size_Type;
 198      crc_32 : Interfaces.Unsigned_32;
 199      work_password : Ada.Strings.Unbounded.Unbounded_String :=
 200        Ada.Strings.Unbounded.To_Unbounded_String (password);
 201      dummy_name_encoding : Zip.Zip_Name_Encoding;
 202  
 203    begin
 204      if Ignore_Directory then
 205        Zip.Find_Offset_without_Directory
 206          (info          => from,
 207           name          => what,
 208           name_encoding => dummy_name_encoding,
 209           file_index    => header_index,
 210           comp_size     => comp_size,
 211           uncomp_size   => uncomp_size,
 212           crc_32        => crc_32);
 213      else
 214        Zip.Find_Offset
 215          (info          => from,
 216           name          => what,
 217           name_encoding => dummy_name_encoding,
 218           file_index    => header_index,
 219           comp_size     => comp_size,
 220           uncomp_size   => uncomp_size,
 221           crc_32        => crc_32);
 222      end if;
 223      UnZipFile
 224        (zip_stream      => zip_stream,
 225         header_index    => header_index,
 226         mem_ptr         => mem_ptr,
 227         out_stream_ptr  => out_stream_ptr,
 228         password        => work_password,
 229         hint_comp_size  => comp_size,
 230         hint_crc_32     => crc_32,
 231         cat_uncomp_size => uncomp_size);
 232    end S_Extract;
 233  
 234    -------------------- for exportation:
 235  
 236    procedure Close (File : in out Zipped_File_Type) is
 237    begin
 238      if File = null or else File.state = uninitialized then
 239         raise Use_Error;
 240      end if;
 241      Dispose (File.file_name);
 242      Dispose (File.uncompressed);
 243      Dispose (File);
 244      File := null;
 245    end Close;
 246  
 247    function Name (File : in Zipped_File_Type) return String is
 248    begin
 249      return File.file_name.all;
 250    end Name;
 251  
 252    function Is_Open (File : in Zipped_File_Type) return Boolean is
 253    begin
 254      return File /= null and then File.state /= uninitialized;
 255    end Is_Open;
 256  
 257    function End_Of_File (File : in Zipped_File_Type) return Boolean is
 258    begin
 259      if File = null or else File.state = uninitialized then
 260         raise Use_Error;
 261      end if;
 262      return File.state = end_of_zip;
 263    end End_Of_File;
 264  
 265    procedure Open
 266       (File             : in out Zipped_File_Type;  --  File-in-archive handle
 267        Archive_Info     : in Zip.Zip_Info;          --  Archive's Zip_info
 268        Name             : in String;                --  Name of zipped entry
 269        Password         : in String  := "";         --  Decryption password
 270        Ignore_Directory : in Boolean := False)      --  True: will open Name in first directory found
 271    is
 272      use Zip_Streams, Ada.Streams;
 273      zip_stream   : aliased File_Zipstream;
 274      input_stream : Zipstream_Class_Access;
 275      use_a_file   : constant Boolean := Zip.Zip_Stream (Archive_Info) = null;
 276    begin
 277      if File = null then
 278        File := new UnZip_Stream_Type;
 279      elsif File.state /= uninitialized then  --  forgot to close last time!
 280        raise Use_Error;
 281      end if;
 282      if use_a_file then
 283        input_stream := zip_stream'Unchecked_Access;
 284        Set_Name (zip_stream, Zip.Zip_Name (Archive_Info));
 285        Open (zip_stream, In_File);
 286      else -- use the given stream
 287        input_stream := Zip.Zip_Stream (Archive_Info);
 288      end if;
 289      --
 290      File.archive_info := Archive_Info;  --  Full clone. Now a copy is safely with File.
 291      File.file_name := new String'(Name);
 292      begin
 293        S_Extract (
 294          File.archive_info,
 295          input_stream.all,
 296          Name,
 297          Password,
 298          File.uncompressed,
 299          null,
 300          Ignore_Directory
 301        );
 302        if use_a_file then
 303          Close (zip_stream);
 304        end if;
 305      exception
 306        when others =>
 307          if use_a_file then
 308            Close (zip_stream);
 309          end if;
 310          raise;
 311      end;
 312      File.index := File.uncompressed'First;
 313      File.state := data_uncompressed;
 314      --  Bug fix for data of size 0 - 29-Nov-2002
 315      if File.uncompressed'Last < File.index then -- (1..0) array
 316        File.state := end_of_zip;
 317      end if;
 318    end Open;
 319  
 320    procedure Open
 321       (File             : in out Zipped_File_Type; -- File-in-archive handle
 322        Archive_Name     : in String;               -- Name of archive file
 323        Name             : in String;               -- Name of zipped entry
 324        Password         : in String  := "";        -- Decryption password
 325        Case_sensitive   : in Boolean := False;
 326        Ignore_Directory : in Boolean := False)     -- True: will open Name in first directory found
 327     is
 328      temp_info : Zip.Zip_Info;
 329    begin
 330      Zip.Load (temp_info, Archive_Name, Case_sensitive);
 331      Open (File, temp_info, Name, Password, Ignore_Directory);
 332    end Open;
 333  
 334    procedure Open
 335       (File             : in out Zipped_File_Type; -- File-in-archive handle
 336        Archive_Stream   : in out Zip_Streams.Root_Zipstream_Type'Class; -- Archive's stream
 337        Name             : in String;               -- Name of zipped entry
 338        Password         : in String  := "";        -- Decryption password
 339        Case_sensitive   : in Boolean := False;
 340        Ignore_Directory : in Boolean := False)     -- True: will open Name in first directory found
 341    is
 342      temp_info : Zip.Zip_Info;
 343    begin
 344      Zip.Load (temp_info, Archive_Stream, Case_sensitive);
 345      Open (File, temp_info, Name, Password, Ignore_Directory);
 346    end Open;
 347  
 348    ------------------------------------------
 349    -- Read procedure for Unzip_Stream_Type --
 350    ------------------------------------------
 351  
 352    overriding procedure Read
 353      (Self   : in out UnZip_Stream_Type;
 354       Item   :    out Ada.Streams.Stream_Element_Array;
 355       Last   :    out Ada.Streams.Stream_Element_Offset)
 356    is
 357      use Ada.Streams;
 358    begin
 359      if Self.state = uninitialized then
 360        raise Use_Error;
 361      end if;
 362      if Self.state = end_of_zip then
 363        --  Zero transfer -> Last:= Item'First - 1, see RM 13.13.1(8)
 364        --  No End_Error here, T'Read will raise it: RM 13.13.2(37)
 365        if Item'First > Stream_Element_Offset'First then
 366          Last := Item'First - 1;
 367          return;
 368        else
 369          --  Well, we cannot return Item'First - 1...
 370          raise Constraint_Error; -- RM 13.13.1(11) requires this.
 371        end if;
 372      end if;
 373      if Item'Length = 0 then
 374        --  Nothing to be read actually.
 375        Last := Item'Last; -- this is < Item'First
 376        return;
 377      end if;
 378      --  From now on, we can assume Item'Length > 0.
 379  
 380      if Self.index + Item'Length <= Self.uncompressed'Last then
 381        --  * Normal case: even after reading, the index will be in the range
 382        Last := Item'Last;
 383        Item :=
 384          Self.uncompressed (Self.index .. Self.index + Item'Length - 1);
 385        Self.index := Self.index + Item'Length;
 386        --  Now: Stream.index <= Stream.uncompressed'Last,
 387        --  then at least one element is left to be read, end_of_zip not possible
 388      else
 389        --  * Special case: we exhaust the buffer
 390        Last := Item'First + (Self.uncompressed'Last - Self.index);
 391        Item (Item'First .. Last) :=
 392          Self.uncompressed (Self.index .. Self.uncompressed'Last);
 393        Self.state := end_of_zip;
 394        --  If Last < Item'Last, the T'Read attribute raises End_Error
 395        --  because of the incomplete reading.
 396      end if;
 397    end Read;
 398  
 399    function Stream (File : Zipped_File_Type) return Stream_Access is
 400    begin
 401      return Stream_Access (File);
 402    end Stream;
 403  
 404    procedure Set_Index (File : in Zipped_File_Type; To : in Positive_Count) is
 405      use Ada.Streams;
 406    begin
 407      --  In the RM, A.12.1 The Package Streams.Stream_IO, 1.1/1,
 408      --  the behaviour of the current index is said to be described in A.8.
 409      --
 410      --  In A.8.5, Set_Index's behaviour beyond the size is specified as such:
 411      --     "Sets the current index of the given file to the given index
 412      --      value (which may exceed the current size of the file)."
 413      --
 414      --  Empirical verification: GNAT doesn't set a limit to the index.
 415      --  Only when a read occurs off the actual file limits, an End_Error is
 416      --  raised.
 417      File.index :=
 418         Stream_Element_Offset (To - 1) + File.uncompressed'First;
 419    end Set_Index;
 420  
 421    function Index (File : in Zipped_File_Type) return Positive_Count is
 422      use Ada.Streams;
 423    begin
 424      return Positive_Count (File.index - File.uncompressed'First) + 1;
 425    end Index;
 426  
 427    function Size (File : in Zipped_File_Type) return Count is
 428      comp_size   : Zip.Zip_64_Data_Size_Type;
 429      uncomp_size : Zip.Zip_64_Data_Size_Type;
 430    begin
 431      Zip.Get_Sizes (File.archive_info, File.file_name.all, comp_size, uncomp_size);
 432      return Count (uncomp_size);
 433    end Size;
 434  
 435    overriding procedure Write
 436      (Self   : in out UnZip_Stream_Type;
 437       Item   : in     Ada.Streams.Stream_Element_Array)
 438    is
 439      write_not_supported : exception;
 440    begin
 441      raise write_not_supported;
 442    end Write;
 443  
 444    procedure Extract
 445      (Destination      : in out Ada.Streams.Root_Stream_Type'Class;
 446       Archive_Info     : in Zip.Zip_Info;         --  Archive's Zip_info
 447       Entry_Name       : in String;               --  Name of zipped entry
 448       Password         : in String  := "";        --  Decryption password
 449       Ignore_Directory : in Boolean := False)     --  True: will open Name in first directory found
 450    is
 451      use Zip_Streams;
 452      zip_stream   : aliased File_Zipstream;
 453      input_stream : Zipstream_Class_Access;
 454      use_a_file   : constant Boolean := Zip.Zip_Stream (Archive_Info) = null;
 455    begin
 456      if use_a_file then
 457        input_stream := zip_stream'Unchecked_Access;
 458        Set_Name (zip_stream, Zip.Zip_Name (Archive_Info));
 459        Open (zip_stream, In_File);
 460      else -- use the given stream
 461        input_stream := Zip.Zip_Stream (Archive_Info);
 462      end if;
 463      declare
 464        dummy_mem_ptr : p_Stream_Element_Array;
 465      begin
 466        S_Extract (
 467          Archive_Info,
 468          input_stream.all,
 469          Entry_Name,
 470          Password,
 471          dummy_mem_ptr,
 472          Destination'Unchecked_Access,
 473          Ignore_Directory
 474        );
 475        if use_a_file then
 476          Close (zip_stream);
 477        end if;
 478      exception
 479        when others =>
 480          if use_a_file then
 481            Close (zip_stream);
 482          end if;
 483          raise;
 484      end;
 485    end Extract;
 486  
 487  end UnZip.Streams;

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.