Back to... Zip-Ada

Source file : unzip.adb


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

with Ada.Exceptions;                    use Ada.Exceptions;
with Interfaces;                        use Interfaces;

with Ada.IO_Exceptions;

package body UnZip is

  use Ada.Streams, Ada.Strings.Unbounded;

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

  --------------------------------------------------
  -- *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           : File_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:=
      (write_to_binary_file, write_to_text_file);
    mode: constant array(Boolean) of Write_mode:=
      (bin_text_mode( options(extract_as_text) ), just_test);
    actual_mode: Write_mode:= mode( options(test_only) );

    true_packed_size: File_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. :  unzipada\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)= '/' or n(i)='\' 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)= '/' or composed_name(i)='\' 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)='\' or path(i)='/' 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: File_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, Zip_Streams;

    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 = unknown then
      Ada.Exceptions.Raise_Exception
        (UnZip.Unsupported_method'Identity,
         "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
       );

    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 !
      local_header.dd.crc_32            := hint_crc_32;
      local_header.dd.compressed_size   := hint_comp_size;
      local_header.dd.uncompressed_size := File_size_type'Last;
      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:= File_size_type(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,
          File_size_type(local_header.dd.uncompressed_size)
        );
      end if;
      if the_name_len = 0 or else
        (the_name( the_name_len ) = '/' or
         the_name( the_name_len ) = '\' )
      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,
          File_size_type(local_header.dd.uncompressed_size)
        );
      end if;
      if out_name'Length = 0 or else
        (out_name( out_name'Last ) = '/' or
         out_name( out_name'Last ) = '\' )
      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_Exception(Zip.Archive_corrupted'Identity,
            "End of stream reached (location: between local header and archived data)");
      end;
      UnZip.Decompress.Decompress_data(
        zip_file                   => zip_file,
        format                     => method,
        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),
              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_Exception (Zip.Archive_corrupted'Identity, "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, Zip_Streams;
    zip_file     : File_Zipstream;
                   -- was Unbounded_Stream & file->buffer copy in v.26
    header_index : ZS_Index_Type;
    comp_size    : File_size_type;
    uncomp_size  : File_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    => 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_Exception (Zip.Archive_corrupted'Identity, "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, Zip_Streams;
    zip_file     : aliased File_Zipstream;
                   -- was Unbounded_Stream & file->buffer copy in v.26
    header_index : Zip_Streams.ZS_Index_Type;
    comp_size    : File_size_type;
    uncomp_size  : File_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    => 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_Exception (Zip.Archive_corrupted'Identity, "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, Zip_Streams;
    zip_file     : File_Zipstream;
                   -- was Unbounded_Stream & file->buffer copy in v.26
    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    => IBM_437, -- ignored
        name_from_header     => True,
        header_index         => header_index,
        hint_comp_size       => File_size_type'Last,
        --                      ^ 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.Zip_file_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    : File_size_type;
    uncomp_size  : File_size_type;
    crc_32       : Unsigned_32;
    work_password: Unbounded_String:= To_Unbounded_String(password);
    use Zip, Zip_Streams;
    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_Exception (Zip.Archive_corrupted'Identity, "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    : File_size_type;
    uncomp_size  : File_size_type;
    crc_32       : Unsigned_32;
    work_password: Unbounded_String:= To_Unbounded_String(password);
    use Zip, Zip_Streams;
    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_Exception (Zip.Archive_corrupted'Identity, "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 related Ada projects on Gautier's blog.