Back to... Zip-Ada

Source file : unzip.adb



--  Legal licensing note:

--  Copyright (c) 1999 .. 2023 Gautier de Montmollin
--  SWITZERLAND

--  Permission is hereby granted, free of charge, to any person obtaining a copy
--  of this software and associated documentation files (the "Software"), to deal
--  in the Software without restriction, including without limitation the rights
--  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
--  copies of the Software, and to permit persons to whom the Software is
--  furnished to do so, subject to the following conditions:

--  The above copyright notice and this permission notice shall be included in
--  all copies or substantial portions of the Software.

--  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
--  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
--  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
--  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
--  THE SOFTWARE.

--  NB: this is the MIT License, as found 12-Sep-2007 on the site
--  http://www.opensource.org/licenses/mit-license.php

with Zip.Headers, UnZip.Decompress;
with Zip_Streams;

with Ada.IO_Exceptions;
with Interfaces;

package body UnZip is

  use Ada.Strings.Unbounded, Interfaces;

  boolean_to_encoding : constant array (Boolean) of Zip.Zip_Name_Encoding :=
    (False => Zip.IBM_437, True => Zip.UTF_8);

  fallback_compressed_size : constant := 16#FFFF_FFFF#;

  --------------------------------------------------
  -- *The* internal 1-file unzipping procedure.   --
  -- Input must be _open_ and won't be _closed_ ! --
  --------------------------------------------------

  procedure UnZipFile
    (zip_file                 : in out Zip_Streams.Root_Zipstream_Type'Class;
     out_name                 : String;
     out_name_encoding        : Zip.Zip_Name_Encoding;
     name_from_header         : Boolean;
     header_index             : in out Zip_Streams.ZS_Index_Type;
     hint_comp_size           : Zip.Zip_64_Data_Size_Type; -- Added 2007 for .ODS files
     hint_crc_32              : Unsigned_32;    -- Added 2012 for decryption
     feedback                 : Zip.Feedback_Proc;
     help_the_file_exists     : Resolve_Conflict_Proc;
     tell_data                : Tell_Data_Proc;
     get_pwd                  : Get_Password_Proc;
     options                  : Option_Set;
     password                 : in out Unbounded_String;
     file_system_routines     : FS_Routines_Type)
  is
    work_index : Zip_Streams.ZS_Index_Type := header_index;
    local_header : Zip.Headers.Local_File_Header;
    data_descriptor_after_data : Boolean;
    method : PKZip_Method;

    skip_this_file : Boolean := False;
    bin_text_mode : constant array (Boolean) of Write_Mode_Type :=
      (write_to_binary_file, write_to_text_file);
    mode : constant array (Boolean) of Write_Mode_Type :=
      (bin_text_mode (options (extract_as_text)), just_test);
    actual_mode : Write_Mode_Type := mode (options (test_only));

    true_packed_size : Zip.Zip_64_Data_Size_Type;  --  encryption adds 12 to packed size

    the_output_name : Unbounded_String;

    --  27-Jun-2001 : possibility of trashing directory part of a name
    --                e.g. :  zipada/uza_src/unzip.ads -> unzip.ads
    function Maybe_trash_dir (n : String) return String is
      idx : Integer := n'First - 1;
    begin
      if options (junk_directories) then
        for i in n'Range loop
          if n (i) in '/' | '\' then
            idx := i;
          end if;
        end loop;
        --  idx points on the index just before the interesting part
        return n (idx + 1 .. n'Last);
      else
        return n;
      end if;
    end Maybe_trash_dir;

    procedure Set_definitively_named_outfile (composed_name : String) is
      idx : Integer := composed_name'First - 1;
      first_in_name : Integer;
    begin
      for i in composed_name'Range loop
        if composed_name (i) in '/' | '\' then
          idx := i;
        end if;
      end loop;
      --  idx points on the index just before the name part

      if idx >= composed_name'First and then
         actual_mode in Write_to_file and then
         file_system_routines.Create_Path /= null
      then
        --  Not only the name, also a path.
        --  In that case, we may need to create parts of the path.
        declare
          Directory_Separator : constant Character := '/';
          --  The '/' separator is also recognized by Windows' routines,
          --  so we can just use it as a standard. See the discussion started
          --  in July 2010 in the Ada Comment mailing list about it
          --  for the 2012 standard.
          path : String := composed_name (composed_name'First .. idx - 1);
        begin
          --  Set the file separator recognized by the O.S.
          for i in path'Range loop
            if path (i) in '/' | '\' then
              path (i) := Directory_Separator;
            end if;
          end loop;
          if path = "" then
            null;
          elsif path (path'Last) = ':' then
            null; -- We are on Windows and cannot create drives (like "D:")
          else
            file_system_routines.Create_Path (path);
          end if;
        end;
      end if;
      --  Now we can create the file itself.
      first_in_name := composed_name'First;
      --
      the_output_name :=
        To_Unbounded_String (composed_name (first_in_name .. composed_name'Last));
    end Set_definitively_named_outfile;

    function Full_Path_Name (
      file_name_in_archive : String;
      encoding             : Zip.Zip_Name_Encoding)
    return String
    is
    begin
       if file_system_routines.Compose_File_Name = null then
          return file_name_in_archive;
       else
          return file_system_routines.Compose_File_Name (file_name_in_archive, encoding);
       end if;
    end Full_Path_Name;

    procedure Set_outfile (
      long_not_composed_name : String;
      encoding               : Zip.Zip_Name_Encoding
    )
    is
      --  Eventually trash the archived directory structure, then
      --  eventually add/modify/... another one:
      name : constant String :=
        Full_Path_Name (Maybe_trash_dir (long_not_composed_name), encoding);
    begin
      Set_definitively_named_outfile (name);
    end Set_outfile;

    procedure Set_outfile_interactive (
      long_not_composed_possible_name : String;
      encoding                        : Zip.Zip_Name_Encoding
    )
    is
      --  Eventually trash the archived directory structure, then
      --  eventually add/modify/... another one:
      possible_name : constant String :=
        Full_Path_Name (Maybe_trash_dir (long_not_composed_possible_name), encoding);
      --  possible_name may have a different encoding depending on Compose_File_Name...
      new_name : String (1 .. 1024);
      new_name_length : Natural;
    begin
      if help_the_file_exists /= null and then Zip.Exists (possible_name) then
        loop
          case current_user_attitude is
            when yes | no | rename_it => -- then ask for this name too
              help_the_file_exists (
                long_not_composed_possible_name, encoding,
                current_user_attitude,
                new_name, new_name_length
              );
            when yes_to_all | none | abort_now =>
              exit; -- nothing to decide: previous decision was definitive
          end case;
          exit when not (
            current_user_attitude = rename_it and then -- new name exists too!
            Zip.Exists (new_name (1 .. new_name_length))
          );
        end loop;

        --  User has decided.
        case current_user_attitude is
          when yes | yes_to_all =>
            skip_this_file := False;
            Set_definitively_named_outfile (possible_name);
          when no | none =>
            skip_this_file := True;
          when rename_it =>
            skip_this_file := False;
            Set_definitively_named_outfile (new_name (1 .. new_name_length));
          when abort_now =>
            raise User_abort;
        end case;

      else -- no name conflict or non-interactive (help_the_file_exists=null)

        skip_this_file := False;
        Set_definitively_named_outfile (possible_name);
      end if;
    end Set_outfile_interactive;

    procedure Inform_User (
      name : String;
      comp, uncomp : Zip.Zip_64_Data_Size_Type
    )
    is
    begin
      if tell_data /= null  then
        tell_data (name, comp, uncomp, method);
      end if;
    end Inform_User;

    the_name     : String (1 .. 65_535);  --  Seems overkill, but Zip entry names can be that long!
    the_name_len : Natural;
    use Zip_Streams;
    use type Zip.PKZip_method;
    use type Zip.Feedback_Proc;

    actual_feedback : Zip.Feedback_Proc;

    dummy_memory : p_Stream_Element_Array;
    dummy_stream : constant p_Stream := null;
    encrypted, dummy_bool : Boolean;

  begin
    begin
      Set_Index (zip_file, work_index);
      Zip.Headers.Read_and_Check (zip_file, local_header);
    exception
      when Zip.Headers.bad_local_header =>
        raise;  --  Processed later, on Extract
      when others =>
        raise Zip.Archive_corrupted;
    end;

    method := Zip.Method_from_Code (local_header.zip_type);
    if method = Zip.unknown then
      raise UnZip.Unsupported_method with
         "Format (method) #" & Unsigned_16'Image (local_header.zip_type) &
         " is unknown";
    end if;

    --  calculate offset of data

    work_index :=
      work_index +
      ZS_Size_Type (
             local_header.filename_length    +
             local_header.extra_field_length +
             Zip.Headers.local_header_length
      );

    --
    --  Zip64 extension.
    --
    if local_header.extra_field_length >= 4 then
      declare
        mem                    : constant Zip_Streams.ZS_Index_Type := Index (zip_file);
        local_header_extension : Zip.Headers.Local_File_Header_Extension;
        dummy_offset           : Unsigned_64 := 0;  --  Initialized for avoiding random value = 16#FFFF_FFFF#
      begin
        Set_Index (zip_file, mem + Zip_Streams.ZS_Index_Type (local_header.filename_length));
        Zip.Headers.Read_and_Check (zip_file, local_header_extension);
        Set_Index (zip_file, mem);
        Zip.Headers.Interpret
          (local_header_extension,
           local_header.dd.uncompressed_size,
           local_header.dd.compressed_size,
           dummy_offset);
      end;
    end if;

    data_descriptor_after_data := (local_header.bit_flag and 8) /= 0;

    if data_descriptor_after_data then
      --  Sizes and CRC are stored after the data
      --  We set size to avoid getting a sudden Zip_EOF !
      if local_header.zip_type = 0
        and then hint_comp_size = fallback_compressed_size
      then
        --  For Stored (Method 0) data we need a correct "compressed" size.
        --  If the hint is the bogus fallback value, it is better to trust
        --  the local header, since this size is known in advance. Case found
        --  in Microsoft's OneDrive cloud storage (in 2018). Zip files,
        --  created by the server for downloading more than one file, are
        --  using the "Store" format and a postfixed Data Descriptor for
        --  writing the CRC value.
        --
        null;  --  Do not overwrite the compressed size in that case.
      else
        local_header.dd.compressed_size := hint_comp_size;
      end if;
      local_header.dd.crc_32            := hint_crc_32;
      local_header.dd.uncompressed_size := fallback_compressed_size;
      actual_feedback := null;  --  no feedback possible: unknown sizes
    else
      --  Sizes and CRC are stored before the data, inside the local header
      actual_feedback := feedback;  --  use the given feedback procedure
    end if;

    encrypted := (local_header.bit_flag and Zip.Headers.Encryption_Flag_Bit) /= 0;

    --  13-Dec-2002
    true_packed_size := local_header.dd.compressed_size;
    if encrypted then
      true_packed_size := true_packed_size - 12;
    end if;

    if name_from_header then  --  Name from local header is used as output name
      the_name_len := Natural (local_header.filename_length);
      if the_name_len > 0 then
        String'Read (zip_file'Access, the_name (1 .. the_name_len));
      end if;
      if not data_descriptor_after_data then
        Inform_User (
          the_name (1 .. the_name_len),
          true_packed_size,
          local_header.dd.uncompressed_size
        );
      end if;
      if the_name_len = 0 or else the_name (the_name_len) in '/' | '\' then
        --  This is a directory name (12-feb-2000)
        skip_this_file := True;
      elsif actual_mode in Write_to_file then
        Set_outfile_interactive (
          the_name (1 .. the_name_len),
          boolean_to_encoding ((local_header.bit_flag and
           Zip.Headers.Language_Encoding_Flag_Bit) /= 0)
        );
      else -- only informational, no need for interaction
        Set_outfile (the_name (1 .. the_name_len),
          boolean_to_encoding ((local_header.bit_flag and
           Zip.Headers.Language_Encoding_Flag_Bit) /= 0)
        );
      end if;
    else -- Output name is given: out_name
      if not data_descriptor_after_data then
        Inform_User (
          out_name,
          true_packed_size,
          local_header.dd.uncompressed_size
        );
      end if;
      if out_name'Length = 0 or else out_name (out_name'Last) in '/' | '\' then
        --  This is a directory name, so do not write anything (30-Jan-2012).
        skip_this_file := True;
      elsif actual_mode in Write_to_file then
        Set_outfile_interactive (out_name, out_name_encoding);
      else -- only informational, no need for interaction
        Set_outfile (out_name, out_name_encoding);
      end if;
    end if;

    if skip_this_file then
      actual_mode := just_test;
    end if;

    if skip_this_file and not data_descriptor_after_data then
      --  We can skip actually since sizes are known.
      if feedback /= null then
        feedback (
          percents_done => 0,
          entry_skipped => True,
          user_abort    => dummy_bool
        );
      end if;
    else
      begin
        Set_Index (zip_file, work_index);  --  eventually skips the file name
      exception
        when others =>
          raise Zip.Archive_corrupted with
            "End of stream reached (location: between local header and archived data)";
      end;
      UnZip.Decompress.Decompress_Data (
        zip_file                   => zip_file,
        format                     => method,
        write_mode                 => actual_mode,
        output_file_name           => To_String (the_output_name),
        output_memory_access       => dummy_memory,
        output_stream_access       => dummy_stream,
        feedback                   => actual_feedback,
        explode_literal_tree       => (local_header.bit_flag and 4) /= 0,
        explode_slide_8KB_LZMA_EOS => (local_header.bit_flag and Zip.Headers.LZMA_EOS_Flag_Bit) /= 0,
        data_descriptor_after_data => data_descriptor_after_data,
        is_encrypted               => encrypted,
        password                   => password,
        get_new_password           => get_pwd,
        hint                       => local_header
      );

      if actual_mode /= just_test then
        begin
          if file_system_routines.Set_Time_Stamp /= null then
            file_system_routines.Set_Time_Stamp (
              To_String (the_output_name),
              Zip.Convert (local_header.file_timedate)
            );
          elsif file_system_routines.Set_ZTime_Stamp /= null then
            file_system_routines.Set_ZTime_Stamp (
              To_String (the_output_name),
              local_header.file_timedate
            );
          end if;
        exception
          when Zip_Streams.Calendar.Time_Error | Ada.Calendar.Time_Error =>
            null; -- invalid time, we give up setting the time stamp
        end;
      end if;

      if data_descriptor_after_data then -- Sizes and CRC at the end
        --  Inform after decompression
        Inform_User (
          To_String (the_output_name),
          local_header.dd.compressed_size,
          local_header.dd.uncompressed_size
        );
      end if;

    end if; -- not ( skip_this_file and not data_descriptor )

    --  Set the offset on the next zipped file
    header_index := header_index +
        ZS_Size_Type (
              local_header.filename_length    +
              local_header.extra_field_length +
              Zip.Headers.local_header_length
        ) +
        ZS_Size_Type (local_header.dd.compressed_size);

    if data_descriptor_after_data then
      header_index :=
        header_index + ZS_Size_Type (Zip.Headers.data_descriptor_length);
    end if;

  exception
    when Ada.IO_Exceptions.End_Error =>
      raise Zip.Archive_corrupted with "End of stream reached";
  end UnZipFile;

  ----------------------------------
  -- Simple extraction procedures --
  ----------------------------------

  --  Extract all files from an archive (from)

  procedure Extract (from                 : String;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
  begin
    Extract (from, null, null, null, null,
             options, password, file_system_routines);
  end Extract;

  procedure Extract (from                 : String;
                     what                 : String;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
  begin
    Extract (from, what, null, null, null, null,
             options, password, file_system_routines);
  end Extract;

  procedure Extract (from                 : String;
                     what                 : String;
                     rename               : String;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
  begin
    Extract (from, what, rename, null, null, null,
             options, password, file_system_routines);
  end Extract;

  procedure Extract (from                 : Zip.Zip_Info;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
  begin
    Extract (from, null, null, null, null,
             options, password, file_system_routines);
  end Extract;

  procedure Extract (from                 : Zip.Zip_Info;
                     what                 : String;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
  begin
    Extract (from, what, null, null, null, null,
             options, password, file_system_routines);
  end Extract;

  procedure Extract (from                 : Zip.Zip_Info;
                     what                 : String;
                     rename               : String;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
  begin
    Extract (from, what, rename, null, null, null,
             options, password, file_system_routines);
  end Extract;

  --  All previous extract call the following ones, with bogus UI arguments

  ------------------------------------------------------------
  -- All previous extraction procedures, for user interface --
  ------------------------------------------------------------

  --  Extract one precise file (what) from an archive (from)

  procedure Extract (from                 : String;
                     what                 : String;
                     feedback             : Zip.Feedback_Proc;
                     help_the_file_exists : Resolve_Conflict_Proc;
                     tell_data            : Tell_Data_Proc;
                     get_pwd              : Get_Password_Proc;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
    use Zip_Streams;
    use type Zip.Feedback_Proc;
    zip_file      : File_Zipstream;
    header_index  : ZS_Index_Type;
    comp_size     : Zip.Zip_64_Data_Size_Type;
    uncomp_size   : Zip.Zip_64_Data_Size_Type;
    crc_32        : Unsigned_32;
    work_password : Unbounded_String := To_Unbounded_String (password);
  begin
    if feedback = null then
      current_user_attitude := yes_to_all; -- non-interactive
    end if;
    Set_Name (zip_file, from);
    Open (zip_file, In_File);
    Zip.Find_Offset
      (file           => zip_file,
       name           => what,
       case_sensitive => options (case_sensitive_match),
       file_index     => header_index,
       comp_size      => comp_size,
       uncomp_size    => uncomp_size,
       crc_32         => crc_32);
    --
    UnZipFile
      (zip_file             => zip_file,
       out_name             => what,
       out_name_encoding    => Zip.IBM_437, -- assumption...
       name_from_header     => False,
       header_index         => header_index,
       hint_comp_size       => comp_size,
       hint_crc_32          => crc_32,
       feedback             => feedback,
       help_the_file_exists => help_the_file_exists,
       tell_data            => tell_data,
       get_pwd              => get_pwd,
       options              => options,
       password             => work_password,
       file_system_routines => file_system_routines);
    --
    Close (zip_file);
  exception
    when Zip.Headers.bad_local_header =>
      raise Zip.Archive_corrupted with "Bad local header";
  end Extract;

  --  Extract one precise file (what) from an archive (from),
  --  but save under a new name (rename)

  procedure Extract (from                 : String;
                     what                 : String;
                     rename               : String;
                     feedback             : Zip.Feedback_Proc;
                     tell_data            : Tell_Data_Proc;
                     get_pwd              : Get_Password_Proc;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
    use Zip_Streams;
    use type Zip.Feedback_Proc;
    zip_file      : aliased File_Zipstream;
    header_index  : Zip_Streams.ZS_Index_Type;
    comp_size     : Zip.Zip_64_Data_Size_Type;
    uncomp_size   : Zip.Zip_64_Data_Size_Type;
    crc_32        : Unsigned_32;
    work_password : Unbounded_String := To_Unbounded_String (password);
  begin
    if feedback = null then
      current_user_attitude := yes_to_all;  --  non-interactive
    end if;
    Set_Name (zip_file, from);
    Open (zip_file, In_File);
    Zip.Find_Offset
      (file           => zip_file,
       name           => what,
       case_sensitive => options (case_sensitive_match),
       file_index     => header_index,
       comp_size      => comp_size,
       uncomp_size    => uncomp_size,
       crc_32         => crc_32);
    --
    UnZipFile
      (zip_file             => zip_file,
       out_name             => rename,
       out_name_encoding    => Zip.IBM_437,  --  assumption...
       name_from_header     => False,
       header_index         => header_index,
       hint_comp_size       => comp_size,
       hint_crc_32          => crc_32,
       feedback             => feedback,
       help_the_file_exists => null,
       tell_data            => tell_data,
       get_pwd              => get_pwd,
       options              => options,
       password             => work_password,
       file_system_routines => file_system_routines);
    --
    Close (zip_file);
  exception
    when Zip.Headers.bad_local_header =>
      raise Zip.Archive_corrupted with "Bad local header";
  end Extract;

  --  Extract all files from an archive (from)

  procedure Extract (from                 : String;
                     feedback             : Zip.Feedback_Proc;
                     help_the_file_exists : Resolve_Conflict_Proc;
                     tell_data            : Tell_Data_Proc;
                     get_pwd              : Get_Password_Proc;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
    use Zip_Streams;
    use type Zip.Feedback_Proc;
    zip_file      : File_Zipstream;
    header_index  : Zip_Streams.ZS_Index_Type;
    work_password : Unbounded_String := To_Unbounded_String (password);
  begin
    if feedback = null then
      current_user_attitude := yes_to_all; -- non-interactive
    end if;
    Set_Name (zip_file, from);
    Open (zip_file, In_File);
    Zip.Find_first_Offset (zip_file, header_index); -- >= 13-May-2001
    --  We simply unzip everything sequentially, until the end:
    all_files : loop
      UnZipFile
        (zip_file             => zip_file,
         out_name             => "",
         out_name_encoding    => Zip.IBM_437, -- ignored
         name_from_header     => True,
         header_index         => header_index,
         hint_comp_size       => fallback_compressed_size,
         --                      ^ no better hint available if comp_size is 0 in local header
         hint_crc_32          => 0, -- 2.0 decryption can fail if data descriptor after data
         feedback             => feedback,
         help_the_file_exists => help_the_file_exists,
         tell_data            => tell_data,
         get_pwd              => get_pwd,
         options              => options,
         password             => work_password,
         file_system_routines => file_system_routines);
    end loop all_files;
  exception
    when Zip.Headers.bad_local_header | Zip.Archive_is_empty =>
      Close (zip_file);  --  Normal case: end of archived entries (of fuzzy data) was hit
    when Zip.Archive_open_error =>
      raise;    --  Couldn't open zip file
    when others =>
      Close (zip_file);
      raise;    --  Something else went wrong
  end Extract;

  --  Extract all files from an archive (from)
  --  Needs Zip.Load(from, ...) prior to the extraction

  procedure Extract (from                 : Zip.Zip_Info;
                     feedback             : Zip.Feedback_Proc;
                     help_the_file_exists : Resolve_Conflict_Proc;
                     tell_data            : Tell_Data_Proc;
                     get_pwd              : Get_Password_Proc;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is
    procedure Extract_1_file (name : String) is
    begin
      Extract
        (from                 => from,
         what                 => name,
         feedback             => feedback,
         help_the_file_exists => help_the_file_exists,
         tell_data            => tell_data,
         get_pwd              => get_pwd,
         options              => options,
         password             => password,
         file_system_routines => file_system_routines);
    end Extract_1_file;
    --
    procedure Extract_all_files is new Zip.Traverse (Extract_1_file);
    --
  begin
    Extract_all_files (from);
  end Extract;

  --  Extract one precise file (what) from an archive (from)
  --  Needs Zip.Load(from, ...) prior to the extraction

  procedure Extract (from                 : Zip.Zip_Info;
                     what                 : String;
                     feedback             : Zip.Feedback_Proc;
                     help_the_file_exists : Resolve_Conflict_Proc;
                     tell_data            : Tell_Data_Proc;
                     get_pwd              : Get_Password_Proc;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines
                ) is

    header_index  : Zip_Streams.ZS_Index_Type;
    comp_size     : Zip.Zip_64_Data_Size_Type;
    uncomp_size   : Zip.Zip_64_Data_Size_Type;
    crc_32        : Unsigned_32;
    work_password : Unbounded_String := To_Unbounded_String (password);
    use Zip_Streams;
    use type Zip.Feedback_Proc;
    zip_file      : aliased File_Zipstream;
    input_stream  : Zipstream_Class_Access;
    use_a_file    : constant Boolean := Zip.Zip_Stream (from) = null;
    name_encoding : Zip.Zip_Name_Encoding;
  begin
    if use_a_file then
      input_stream := zip_file'Unchecked_Access;
      Set_Name (zip_file, Zip.Zip_Name (from));
      Open (zip_file, In_File);
    else -- use the given stream
      input_stream := Zip.Zip_Stream (from);
    end if;
    if feedback = null then
      current_user_attitude := yes_to_all; -- non-interactive
    end if;
    Zip.Find_Offset
      (info          => from,
       name          => what,
       name_encoding => name_encoding,
       file_index    => header_index,
       comp_size     => comp_size,
       uncomp_size   => uncomp_size,
       crc_32        => crc_32);
    --
    UnZipFile
      (zip_file              => input_stream.all,
       out_name              => what,
       out_name_encoding     => name_encoding,
       name_from_header      => False,
       header_index          => header_index,
       hint_comp_size        => comp_size,
       hint_crc_32           => crc_32,
       feedback              => feedback,
       help_the_file_exists  => help_the_file_exists,
       tell_data             => tell_data,
       get_pwd               => get_pwd,
       options               => options,
       password              => work_password,
       file_system_routines  => file_system_routines);
    --
    if use_a_file then
      Close (zip_file);
    end if;
  exception
    when Zip.Headers.bad_local_header =>
      if use_a_file and then Is_Open (zip_file) then
        Close (zip_file);
      end if;
      raise Zip.Archive_corrupted with "Bad local header";
    when others =>
      if use_a_file and then Is_Open (zip_file) then
        Close (zip_file);
      end if;
      raise;
  end Extract;

  --  Extract one precise file (what) from an archive (from)
  --  but save under a new name (rename)
  --  Needs Zip.Load(from, ...) prior to the extraction

  procedure Extract (from                 : Zip.Zip_Info;
                     what                 : String;
                     rename               : String;
                     feedback             : Zip.Feedback_Proc;
                     tell_data            : Tell_Data_Proc;
                     get_pwd              : Get_Password_Proc;
                     options              : Option_Set := no_option;
                     password             : String := "";
                     file_system_routines : FS_Routines_Type := null_routines)
  is

    header_index  : Zip_Streams.ZS_Index_Type;
    comp_size     : Zip.Zip_64_Data_Size_Type;
    uncomp_size   : Zip.Zip_64_Data_Size_Type;
    crc_32        : Unsigned_32;
    work_password : Unbounded_String := To_Unbounded_String (password);
    use Zip_Streams;
    use type Zip.Feedback_Proc;
    zip_file      : aliased File_Zipstream;
    input_stream  : Zipstream_Class_Access;
    use_a_file    : constant Boolean := Zip.Zip_Stream (from) = null;
    name_encoding : Zip.Zip_Name_Encoding;
  begin
    if use_a_file then
      input_stream := zip_file'Unchecked_Access;
      Set_Name (zip_file, Zip.Zip_Name (from));
      Open (zip_file, In_File);
    else  --  use the given stream
      input_stream := Zip.Zip_Stream (from);
    end if;
    if feedback = null then
      current_user_attitude := yes_to_all;  --  non-interactive
    end if;
    Zip.Find_Offset
      (info          => from,
       name          => what,
       name_encoding => name_encoding,
       file_index    => header_index,
       comp_size     => comp_size,
       uncomp_size   => uncomp_size,
       crc_32        => crc_32);
    --
    UnZipFile
      (zip_file             => input_stream.all,
       out_name             => rename,
       out_name_encoding    => name_encoding, -- assumption: encoding same as name
       name_from_header     => False,
       header_index         => header_index,
       hint_comp_size       => comp_size,
       hint_crc_32          => crc_32,
       feedback             => feedback,
       help_the_file_exists => null,
       tell_data            => tell_data,
       get_pwd              => get_pwd,
       options              => options,
       password             => work_password,
       file_system_routines => file_system_routines);
    --
    if use_a_file then
      Close (zip_file);
    end if;
  exception
    when Zip.Headers.bad_local_header =>
      if use_a_file and then Is_Open (zip_file) then
        Close (zip_file);
      end if;
      raise Zip.Archive_corrupted with "Bad local header";
    when others =>
      if use_a_file and then Is_Open (zip_file) then
        Close (zip_file);
      end if;
      raise;
  end Extract;

end UnZip;


Zip-Ada: Ada library for zip archive files (.zip). Ada programming.
Some news about Zip-Ada and other Ada projects on Gautier's blog.