Program MiniUnz;

{ mini unzip demo package by Gilles Vollant

  Usage : miniunz [-exvlo] file.zip [file_to_extract]

  -l or -v list the content of the zipfile.
        -e extract a specific file or all files if [file_to_extract] is missing
        -x like -e, but extract without path information
        -o overwrite an existing file without warning

  Pascal tranlastion
  Copyright (C) 2000 by Jacques Nomssi Nzali
  For conditions of distribution and use, see copyright notice in readme.txt
}

{$ifdef WIN32}
  {$define Delphi}
  {$ifndef FPC}
    {$define Delphi32}
  {$endif}
{$endif}

uses
  {$ifdef Delphi}
  SysUtils, Windows,
  {$else}
  WinDos, strings,
  {$endif}
  zutil,
  zlib, ziputils,
  unzip;

const
  CASESENSITIVITY = 0;
  WRITEBUFFERSIZE = 8192;


{ change_file_date : change the date/time of a file
    filename : the filename of the file where date/time must be modified
    dosdate : the new date at the MSDos format (4 bytes)
    tmu_date : the SAME new date at the tm_unz format }

procedure change_file_date(const filename : PChar;
                           dosdate : uLong;
                           tmu_date : tm_unz);
{$ifdef Delphi32}
var
  hFile : THandle;
  ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite : TFileTime;
begin
  hFile := CreateFile(filename,GENERIC_READ or GENERIC_WRITE,
                      0,NIL,OPEN_EXISTING,0,0);
  GetFileTime(hFile, @ftCreate, @ftLastAcc, @ftLastWrite);
  DosDateTimeToFileTime(WORD((dosdate shl 16)), WORD(dosdate), ftLocal);
  LocalFileTimeToFileTime(ftLocal, ftm);
  SetFileTime(hFile,@ftm, @ftLastAcc, @ftm);
  CloseHandle(hFile);
end;
{$else}
  {$ifdef FPC}
var
  hFile : THandle;
  ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite : TFileTime;
begin
  hFile := CreateFile(filename,GENERIC_READ or GENERIC_WRITE,
                      0,NIL,OPEN_EXISTING,0,0);
  GetFileTime(hFile, @ftCreate, @ftLastAcc, @ftLastWrite);
  DosDateTimeToFileTime(WORD((dosdate shl 16)), WORD(dosdate), @ftLocal);
  LocalFileTimeToFileTime(ftLocal, @ftm);
  SetFileTime(hFile,ftm, ftLastAcc, ftm);
  CloseHandle(hFile);
end;
  {$else} {  msdos }
var
  f: file;
begin
  Assign(f, filename);
  Reset(f, 1); { open file for reading }
   { (Otherwise, close will update time) }
  SetFTime(f,dosDate);
  Close(f);
end;
  {$endif}
{$endif}


{ mymkdir and change_file_date are not 100 % portable
  As I don't know well Unix, I wait feedback for the unix portion }

function mymkdir(dirname : PChar) : boolean;
var
  S : string;
begin
  S := StrPas(dirname);
  {$I-}
  mkdir(S);
  mymkdir := IOresult = 0;
end;

function makedir (newdir : PChar) : boolean;
var
  buffer  : PChar;
  p : PChar;
  len : int;
var
  hold : char;
begin
  makedir := false;
  len := strlen(newdir);

  if (len <= 0) then
    exit;

  buffer := PChar(zcalloc (NIL, len+1, 1));

  strcopy(buffer,newdir);

  if (buffer[len-1] = '/') then
    buffer[len-1] := #0;

  if mymkdir(buffer) then
  begin
    if Assigned(buffer) then
      zcfree(NIL, buffer);
    makedir := true;
    exit;
  end;

  p := buffer+1;
  while true do
  begin
    while( (p^<>#0) and (p^ <> '\') and (p^ <> '/') ) do
      Inc(p);
    hold := p^;
    p^ := #0;
    if (not mymkdir(buffer)) {and (errno = ENOENT)} then
    begin
      WriteLn('couldn''t create directory ',buffer);
      if Assigned(buffer) then
        zcfree(NIL, buffer);
      exit;
    end;
    if (hold = #0) then
      break;
    p^ := hold;
    Inc(p);
  end;
  if Assigned(buffer) then
    zcfree(NIL, buffer);
  makedir := true;
end;

procedure do_banner;
begin
  WriteLn('MiniUnz 0.15, demo package written by Gilles Vollant');
  WriteLn('Pascal port by Jacques Nomssi Nzali');
  WriteLn('more info at http://wwww.tu-chemnitz.de/~nomssi/paszlib.html');
  WriteLn;
end;

procedure do_help;
begin
  WriteLn('Usage : miniunz [-exvlo] file.zip [file_to_extract]');
  WriteLn;
end;

function LeadingZero(w : Word) : String;
var
  s : String;
begin
  Str(w:0,s);
  if Length(s) = 1 then
    s := '0' + s;
  LeadingZero := s;
end;

function HexToStr(w : long) : string;
const
  ByteToChar : array[0..$F] of char ='0123456789ABCDEF';
var
  s : string;
  i : int;
  x : long;
begin
  s := '';
  x := w;
  for i := 0 to 3 do
  begin
    s := ByteToChar[Byte(x) shr 4] + ByteToChar[Byte(x) and $F] + s;
    x := x shr 8;
  end;
  HexToStr := s;
end;

function do_list(uf : unzFile) : int;
var
  i : uLong;
  gi : unz_global_info;
  err : int;
var
  filename_inzip : array[0..255] of char;
  file_info : unz_file_info;
  ratio : uLong;
  string_method : string[255];
var
  iLevel : uInt;
begin
  err := unzGetGlobalInfo(uf, gi);
  if (err <> UNZ_OK) then
    WriteLn('error ',err,' with zipfile in unzGetGlobalInfo');
  WriteLn(' Length  Method   Size  Ratio   Date    Time   CRC-32     Name');
  WriteLn(' ------  ------   ----  -----   ----    ----   ------     ----');
  for i := 0 to gi.number_entry-1 do
  begin
    ratio := 0;
    err := unzGetCurrentFileInfo(uf, @file_info, filename_inzip, sizeof(filename_inzip),NIL,0,NIL,0);
    if (err <> UNZ_OK) then
    begin
      WriteLn('error ',err,' with zipfile in unzGetCurrentFileInfo');
      break;
    end;
    if (file_info.uncompressed_size>0) then
      ratio := (file_info.compressed_size*100) div file_info.uncompressed_size;

    if (file_info.compression_method=0) then
      string_method := 'Stored'
    else
    if (file_info.compression_method=Z_DEFLATED) then
    begin
      iLevel := uInt((file_info.flag and $06) div 2);
      Case iLevel of
       0: string_method := 'Defl:N';
       1: string_method := 'Defl:X';
       2,3: string_method := 'Defl:F'; { 2:fast , 3 : extra fast}
       else
         string_method := 'Unkn. ';
      end;
    end;

    WriteLn(file_info.uncompressed_size:7, '  ',
          string_method:6, ' ',
          file_info.compressed_size:7, ' ',
	  ratio:3,'%  ',  LeadingZero(uLong(file_info.tmu_date.tm_mon)+1),'-',
          LeadingZero(uLong(file_info.tmu_date.tm_mday)):2,'-',
	  LeadingZero(uLong(file_info.tmu_date.tm_year mod 100)):2,'  ',
	  LeadingZero(uLong(file_info.tmu_date.tm_hour)),':',
          LeadingZero(uLong(file_info.tmu_date.tm_min)),'  ',
	  HexToStr(uLong(file_info.crc)),'  ',
          filename_inzip);

    if ((i+1)<gi.number_entry) then
    begin
      err := unzGoToNextFile(uf);
      if (err <> UNZ_OK) then
      begin
        WriteLn('error ',err,' with zipfile in unzGoToNextFile');
        break;
      end;
    end;
  end;

  do_list := 0;
end;


function do_extract_currentfile(
    uf : unzFile;
    const popt_extract_without_path : int;
    var popt_overwrite : int) : int;
var
  filename_inzip : packed array[0..255] of char;
  filename_withoutpath : PChar;
  p: PChar;
  err : int;
  fout : FILEptr;
  buf : pointer;
  size_buf : uInt;
  file_info : unz_file_info;
var
  write_filename : PChar;
  skip : int;
var
  rep : char;
  ftestexist : FILEptr;
var
  answer : string[127];
var
  c : char;
begin
  fout := NIL;

  err := unzGetCurrentFileInfo(uf, @file_info, filename_inzip,
    sizeof(filename_inzip), NIL, 0, NIL,0);

  if (err <> UNZ_OK) then
  begin
    WriteLn('error ',err, ' with zipfile in unzGetCurrentFileInfo');
    do_extract_currentfile := err;
    exit;
  end;

  size_buf := WRITEBUFFERSIZE;
  buf := zcalloc (NIL, size_buf, 1);
  if (buf = NIL) then
  begin
    WriteLn('Error allocating memory');
    do_extract_currentfile := UNZ_INTERNALERROR;
    exit;
  end;

  filename_withoutpath := filename_inzip;
  p := filename_withoutpath;
  while (p^ <> #0) do
  begin
    if (p^='/') or (p^='\') then
      filename_withoutpath := p+1;
    Inc(p);
  end;

  if (filename_withoutpath^=#0) then
  begin
    if (popt_extract_without_path=0) then
    begin
      WriteLn('creating directory: ',filename_inzip);
      mymkdir(filename_inzip);
    end;
  end
  else
  begin

    skip := 0;
    if (popt_extract_without_path=0) then
      write_filename := filename_inzip
    else
      write_filename := filename_withoutpath;

    err := unzOpenCurrentFile(uf);
    if (err <> UNZ_OK) then
      WriteLn('error ',err,' with zipfile in unzOpenCurrentFile');


    if ((popt_overwrite=0) and (err=UNZ_OK)) then
    begin
      rep := #0;
      
      ftestexist := fopen(write_filename,fopenread);
      if (ftestexist <> NIL) then
      begin
        fclose(ftestexist);
	repeat
	  Write('The file ',write_filename,
            ' exist. Overwrite ? [y]es, [n]o, [A]ll: ');
	  ReadLn(answer);

	  rep := answer[1] ;
	  if ((rep>='a') and (rep<='z')) then
	    Dec(rep, $20);
	until (rep = 'Y') or (rep = 'N') or (rep = 'A');
      end;

      if (rep = 'N') then
        skip := 1;

      if (rep = 'A') then
        popt_overwrite := 1;
    end;

    if (skip=0) and (err=UNZ_OK) then
    begin
      fout := fopen(write_filename,fopenwrite);

      { some zipfile don't contain directory alone before file }
      if (fout=NIL) and (popt_extract_without_path=0) and
        (filename_withoutpath <> PChar(@filename_inzip)) then
      begin
        c := (filename_withoutpath-1)^;
        (filename_withoutpath-1)^ := #0;
        makedir(write_filename);
        (filename_withoutpath-1)^ := c;
        fout := fopen(write_filename, fopenwrite);
      end;

      if (fout=NIL) then
        WriteLn('error opening ',write_filename);
    end;

    if (fout <> NIL) then
    begin
      WriteLn(' extracting: ',write_filename);

      repeat
        err := unzReadCurrentFile(uf,buf,size_buf);
	if (err<0) then
	begin
	  WriteLn('error ',err,' with zipfile in unzReadCurrentFile');
	  break;
	end;
	if (err>0) then
        begin
	  if (fwrite(buf,err,1,fout) <> 1) then
	  begin
	    WriteLn('error in writing extracted file');
            err := UNZ_ERRNO;
	    break;
	  end;
        end;
      until (err=0);
      fclose(fout);
      if (err=0) then
        change_file_date(write_filename,file_info.dosDate,
	   file_info.tmu_date);
    end;

    if (err=UNZ_OK) then
    begin
      err := unzCloseCurrentFile (uf);
      if (err <> UNZ_OK) then
	WriteLn('error ',err,' with zipfile in unzCloseCurrentFile')
      else
        unzCloseCurrentFile(uf); { don't lose the error }
    end;
  end;

  if buf <> NIL then
    zcfree(NIL, buf);
  do_extract_currentfile := err;
end;


function do_extract(uf : unzFile;
                    opt_extract_without_path : int;
                    opt_overwrite : int) : int;
var
  i : uLong;
  gi : unz_global_info;
  err : int;
begin
  err := unzGetGlobalInfo (uf, gi);
  if (err <> UNZ_OK) then
    WriteLn('error ',err,' with zipfile in unzGetGlobalInfo ');

  for i:=0 to gi.number_entry-1 do
  begin
    if (do_extract_currentfile(uf, opt_extract_without_path,
                                   opt_overwrite) <> UNZ_OK) then
      break;

    if ((i+1)<gi.number_entry) then
    begin
      err := unzGoToNextFile(uf);
      if (err <> UNZ_OK) then
      begin
	WriteLn('error ',err,' with zipfile in unzGoToNextFile');
        break;
      end;
    end;
  end;

  do_extract := 0;
end;

function do_extract_onefile(uf : unzFile;
                            const filename : PChar;
                            opt_extract_without_path : int;
                            opt_overwrite : int) : int;
begin
  if (unzLocateFile(uf,filename,CASESENSITIVITY) <> UNZ_OK) then
  begin
    WriteLn('file ',filename,' not found in the zipfile');
    do_extract_onefile := 2;
    exit;
  end;

  if (do_extract_currentfile(uf, opt_extract_without_path,
                              opt_overwrite) = UNZ_OK) then
    do_extract_onefile := 0
  else
    do_extract_onefile := 1;
end;

{ -------------------------------------------------------------------- }
function main : int;
const
  zipfilename : PChar = NIL;
  filename_to_extract : PChar = NIL;
var
  i : int;
  opt_do_list : int;
  opt_do_extract : int;
  opt_do_extract_withoutpath : int;
  opt_overwrite : int;
  filename_try : array[0..512-1] of char;
  uf : unzFile;
var
  p : int;
  pstr : string[255];
  c : char;
begin
  opt_do_list := 0;
  opt_do_extract := 1;
  opt_do_extract_withoutpath := 0;
  opt_overwrite := 0;
  uf := NIL;

  do_banner;
  if (ParamCount=0) then
  begin
    do_help;
    Halt(0);
  end
  else
  begin
    for i := 1 to ParamCount do
    begin
      pstr := ParamStr(i);
      if pstr[1]='-' then
      begin
        for p := 2 to Length(pstr) do
        begin
          c := pstr[p];
          Case UpCase(c) of
          'L',
          'V' : opt_do_list := 1;
          'X' : opt_do_extract := 1;
          'E' : begin
                  opt_do_extract := 1;
                  opt_do_extract_withoutpath := 1;
                end;
          'O' : opt_overwrite := 1;
          end;
        end;
      end
      else
      begin
        pstr := pstr + #0;
        if (zipfilename = NIL) then
          zipfilename := StrNew(PChar(@pstr[1]))
        else
          if (filename_to_extract=NIL) then
            filename_to_extract := StrNew(PChar(@pstr[1]));
      end;
    end; { for }
  end;

  if (zipfilename <> NIL) then
  begin
    strcopy(filename_try,zipfilename);
    uf := unzOpen(zipfilename);
    if (uf = NIL) then
    begin
      strcat(filename_try,'.zip');
      uf := unzOpen(filename_try);
    end;
  end;

  if (uf = NIL) then
  begin
    WriteLn('Cannot open ',zipfilename,' or ',zipfilename,'.zip');
    Halt(1);
  end;

  WriteLn(filename_try,' opened');

  if (opt_do_list=1) then
  begin
    main := do_list(uf);
    exit;
  end
  else
    if (opt_do_extract=1) then
    begin
      if (filename_to_extract = NIL) then
      begin
	main := do_extract(uf,opt_do_extract_withoutpath,opt_overwrite);
        exit;
      end
      else
      begin
        main := do_extract_onefile(uf,filename_to_extract,
                    opt_do_extract_withoutpath,opt_overwrite);
        exit;
      end;
    end;

  unzCloseCurrentFile(uf);

  strDispose(zipfilename);
  strDispose(filename_to_extract);
  main := 0;
end;

begin
  main;
  Write('Done...');
  Readln;
end.
