본문 바로가기
DogFoot/Delphi

[Delphi] Zip파일 압축/해제(ft. TZipFile)

by 크림슨킹 2024. 10. 29.

델파이에서 기본 제공하는 TZipFile을 이용하면 다양하게 압축해제가 가능하다.

TZipFile은 System.Zip.pas에 있다.

 

그러나 인터넷을 찾아봐도 별로 유용한 예제가 없어 직접 만들어 사용중인 라이브러리 소스코드를 첨부한다.

다음 기능들을 지원한다.

- 단일 파일, 다중 파일 압축, 폴더 압축 및 압축파일 해제

- 압축 진행상황 파악(TZipProgressNotify)

- UTF8을 이용한 한글 지원

- 압축/해제 암호화 지원(델파이 12이상)

 

델파이12버전 부터는 TZipFile에 IZipCryptor 인터페이스를 통해  압축 및 해제시 암호화를 지원한다.

델파이 하위 버전인 경우에는 Cryptor및 Password 부분을 제거하고 사용하면 된다. 

IZipCryptor의 Cryptor Interface는 PKWAREZipCryptor를 사용하였다.

 

https://github.com/UweRaabe/EncryptedZipFile

(UweRaabe EncryptedZipFile - PKWAREZipCryptor.pas)

 

{******************************************************************************}
{  Application Common Library                                                  }
{                                                                              }
{  Zip Utilities                                                               }
{                                                                              }
{  SJK(UnHERE/CrimsonKing) - unhere@naver.com                                  }
{  - First Update: 2024.10                                                     }
{  - Last Update :                                                             }
{                                                                              }
{******************************************************************************}

unit Lib.Zip;

interface

uses
  Windows, Classes, SysUtils, IOUtils, System.Zip;

  {Example ---------------------------------------------------------------------
    procedure TfrmMain.OnZipProgress(Sender: TObject; FileName: string; TotalCount, CurrentCount: Integer);
    begin
      Memo.Lines.Add(FileName + ': ' + CurrentCount.ToString + '/' + TotalCount.ToString);
      Application.ProcessMessages;
    end;

    procedure TfrmMain.Extract;
    begin
      TZipUtil.Extract(SourceZip, DestDir, Password, OnZipProgress) then
    end;
   ----------------------------------------------------------------------------}

type
  TZipProgressNotify = procedure(Sender: TObject; FileName: string; TotalCount, CurrentCount: Integer) of object;

  TZipUtil = class
  private
    class var
      FZipProgressProc: TZipProgressNotify;
      FTotalCount: Integer;
      FZipCount: Integer;
    class procedure ZipProgressEvent(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
  public
    class function Compress(FileName, ZipFileName: string; Password: string = ''): Boolean; overload;
    class function Compress(FileList: TStrings; ZipFileName: string; Password: string = ''; AProgressProc: TZipProgressNotify = nil): Boolean; overload;
    class function CompressDir(SourcePath, ZipFileName: string; Password: string = ''; AProgressProc: TZipProgressNotify = nil): Boolean;
    class function Extract(ZipFileName, DestPath: string; Password: string = ''; AProgressProc: TZipProgressNotify = nil): Boolean;
  end;

type
  {-----------------------------------------------------------------------------
    UweRaabe EncryptedZipFile - PKWAREZipCryptor.pas
    https://github.com/UweRaabe/EncryptedZipFile
   ----------------------------------------------------------------------------}

  TPKWAREZipCryptor = class(TInterfacedObject, IZipCryptor)
  private const
    { Source: http://www.swissdelphicenter.com/de/showcode.php?id=268 }
    CRC32_TABLE: array [0 .. 255] of longword = ( // dont format
      $00000000, $77073096, $EE0E612C, $990951BA, $076DC419, $706AF48F, $E963A535, $9E6495A3, // dont format
      $0EDB8832, $79DCB8A4, $E0D5E91E, $97D2D988, $09B64C2B, $7EB17CBD, $E7B82D07, $90BF1D91, // dont format
      $1DB71064, $6AB020F2, $F3B97148, $84BE41DE, $1ADAD47D, $6DDDE4EB, $F4D4B551, $83D385C7, // dont format
      $136C9856, $646BA8C0, $FD62F97A, $8A65C9EC, $14015C4F, $63066CD9, $FA0F3D63, $8D080DF5, // dont format
      $3B6E20C8, $4C69105E, $D56041E4, $A2677172, $3C03E4D1, $4B04D447, $D20D85FD, $A50AB56B, // dont format
      $35B5A8FA, $42B2986C, $DBBBC9D6, $ACBCF940, $32D86CE3, $45DF5C75, $DCD60DCF, $ABD13D59, // dont format
      $26D930AC, $51DE003A, $C8D75180, $BFD06116, $21B4F4B5, $56B3C423, $CFBA9599, $B8BDA50F, // dont format
      $2802B89E, $5F058808, $C60CD9B2, $B10BE924, $2F6F7C87, $58684C11, $C1611DAB, $B6662D3D, // dont format
      $76DC4190, $01DB7106, $98D220BC, $EFD5102A, $71B18589, $06B6B51F, $9FBFE4A5, $E8B8D433, // dont format
      $7807C9A2, $0F00F934, $9609A88E, $E10E9818, $7F6A0DBB, $086D3D2D, $91646C97, $E6635C01, // dont format
      $6B6B51F4, $1C6C6162, $856530D8, $F262004E, $6C0695ED, $1B01A57B, $8208F4C1, $F50FC457, // dont format
      $65B0D9C6, $12B7E950, $8BBEB8EA, $FCB9887C, $62DD1DDF, $15DA2D49, $8CD37CF3, $FBD44C65, // dont format
      $4DB26158, $3AB551CE, $A3BC0074, $D4BB30E2, $4ADFA541, $3DD895D7, $A4D1C46D, $D3D6F4FB, // dont format
      $4369E96A, $346ED9FC, $AD678846, $DA60B8D0, $44042D73, $33031DE5, $AA0A4C5F, $DD0D7CC9, // dont format
      $5005713C, $270241AA, $BE0B1010, $C90C2086, $5768B525, $206F85B3, $B966D409, $CE61E49F, // dont format
      $5EDEF90E, $29D9C998, $B0D09822, $C7D7A8B4, $59B33D17, $2EB40D81, $B7BD5C3B, $C0BA6CAD, // dont format
      $EDB88320, $9ABFB3B6, $03B6E20C, $74B1D29A, $EAD54739, $9DD277AF, $04DB2615, $73DC1683, // dont format
      $E3630B12, $94643B84, $0D6D6A3E, $7A6A5AA8, $E40ECF0B, $9309FF9D, $0A00AE27, $7D079EB1, // dont format
      $F00F9344, $8708A3D2, $1E01F268, $6906C2FE, $F762575D, $806567CB, $196C3671, $6E6B06E7, // dont format
      $FED41B76, $89D32BE0, $10DA7A5A, $67DD4ACC, $F9B9DF6F, $8EBEEFF9, $17B7BE43, $60B08ED5, // dont format
      $D6D6A3E8, $A1D1937E, $38D8C2C4, $4FDFF252, $D1BB67F1, $A6BC5767, $3FB506DD, $48B2364B, // dont format
      $D80D2BDA, $AF0A1B4C, $36034AF6, $41047A60, $DF60EFC3, $A867DF55, $316E8EEF, $4669BE79, // dont format
      $CB61B38C, $BC66831A, $256FD2A0, $5268E236, $CC0C7795, $BB0B4703, $220216B9, $5505262F, // dont format
      $C5BA3BBE, $B2BD0B28, $2BB45A92, $5CB36A04, $C2D7FFA7, $B5D0CF31, $2CD99E8B, $5BDEAE1D, // dont format
      $9B64C2B0, $EC63F226, $756AA39C, $026D930A, $9C0906A9, $EB0E363F, $72076785, $05005713, // dont format
      $95BF4A82, $E2B87A14, $7BB12BAE, $0CB61B38, $92D28E9B, $E5D5BE0D, $7CDCEFB7, $0BDBDF21, // dont format
      $86D3D2D4, $F1D4E242, $68DDB3F8, $1FDA836E, $81BE16CD, $F6B9265B, $6FB077E1, $18B74777, // dont format
      $88085AE6, $FF0F6A70, $66063BCA, $11010B5C, $8F659EFF, $F862AE69, $616BFFD3, $166CCF45, // dont format
      $A00AE278, $D70DD2EE, $4E048354, $3903B3C2, $A7672661, $D06016F7, $4969474D, $3E6E77DB, // dont format
      $AED16A4A, $D9D65ADC, $40DF0B66, $37D83BF0, $A9BCAE53, $DEBB9EC5, $47B2CF7F, $30B5FFE9, // dont format
      $BDBDF21C, $CABAC28A, $53B39330, $24B4A3A6, $BAD03605, $CDD70693, $54DE5729, $23D967BF, // dont format
      $B3667A2E, $C4614AB8, $5D681B02, $2A6F2B94, $B40BBE37, $C30C8EA1, $5A05DF1B, $2D02EF8D);
  private
    FKey: array [0 .. 2] of UInt64;
  protected
    function CalcDecryptByte: Byte;
    procedure DecryptByte(var Value: Byte);
    procedure EncryptByte(var Value: Byte);
    procedure InitKeys(const APassword: string);
    function UpdateCRC32(Key: longword; Value: Byte): longword;
    procedure UpdateKeys(Value: Byte);
  public
    procedure Decrypt(var Buffer: TBytes);
    procedure Encrypt(var Buffer: TBytes);
    procedure Init(const APassword: string; AEncrypt: Boolean);
  end;

//==============================================================================
implementation

{ TZipUtil }

class function TZipUtil.Compress(FileName, ZipFileName, Password: string): Boolean;
var
  FileList: TStringList;
begin
  Result := False;
  if not FileExists(FileName) then Exit;

  FileList := TStringList.Create;
  try
    try
      FileList.Add(FileName);
      Result := Self.Compress(FileList, ZipFileName, Password);
    except
      on E: Exception do raise Exception.Create(E.Message);
    end;
  finally
    FileList.Free;
  end;
end;

class function TZipUtil.Compress(FileList: TStrings; ZipFileName, Password: string; AProgressProc: TZipProgressNotify): Boolean;
var
  Zip: TZipFile;
  FileName: string;
  I: Integer;
begin
  Result := False;
  if FileList.Count = 0 then Exit;

  Zip := TZipFile.Create;
  try
    try
      if TFile.Exists(ZipFileName) then
        TFile.Delete(ZipFileName);

      FZipProgressProc := AProgressProc;
      if Assigned(FZipProgressProc) then
      begin
        FTotalCount := FileList.Count;
        FZipCount := 0;
      end;

      Zip.Encoding := TEncoding.UTF8;
      if Password <> '' then
      begin
        Zip.Cryptor := TPKWAREZipCryptor.Create;
        Zip.Password := Password;
      end;
      Zip.Open(ZipFileName, zmWrite);

      I := 1;
      for FileName in FileList do
      begin
        Zip.Add(FileName, ExtractFileName(FileName));
        if Assigned(FZipProgressProc) then
        begin
          FZipProgressProc(nil, FileName, FTotalCount, I);
          Inc(I);
        end;
      end;
      Result := True;
    except
      on E: Exception do raise Exception.Create(E.Message);
    end;
  finally
    Zip.Free;
  end;
end;

class function TZipUtil.CompressDir(SourcePath, ZipFileName, Password: string; AProgressProc: TZipProgressNotify): Boolean;
var
  Zip: TZipFile;
begin
  Result := False;
  if not DirectoryExists(SourcePath) then Exit;

  Zip := TZipFile.Create;
  try
    try
      FZipProgressProc := AProgressProc;
      if Assigned(FZipProgressProc) then
      begin
        FTotalCount := Length(TDirectory.GetFiles(SourcePath, '*.*', GetSearchOption(TSearchOption.soAllDirectories)));
        FZipCount := 0;
      end;

      Zip.Encoding := TEncoding.UTF8;
      if Password <> '' then
      begin
        Zip.Cryptor := TPKWAREZipCryptor.Create;
        Zip.Password := Password;
      end;
      Zip.ZipDirectoryContents(ZipFileName, SourcePath, zcDeflate, ZipProgressEvent);
      Result := True;
    except
      on E: Exception do raise Exception.Create(E.Message);
    end;
  finally
    Zip.Free;
  end;
end;

class function TZipUtil.Extract(ZipFileName, DestPath, Password: string; AProgressProc: TZipProgressNotify): Boolean;
var
  Zip: TZipFile;
begin
  Result := False;
  if not FileExists(ZipFileName) then Exit;
  if not ForceDirectories(DestPath) then Exit;

  Zip := TZipFile.Create;
  try
    try
      Zip.Encoding := TEncoding.UTF8;
      if Password <> '' then
      begin
        Zip.Cryptor := TPKWAREZipCryptor.Create;
        Zip.Password := Password;
      end;

      Zip.Open(ZipFileName, zmRead);
      FZipProgressProc := AProgressProc;
      if Assigned(FZipProgressProc) then
      begin
        Zip.OnProgress := ZipProgressEvent;
        FTotalCount := Zip.FileCount;
        FZipCount := 0;
      end;

      //Zip.ExtractZipFile(ZipFileName, DestPath, ZipProgressEvent); //암호 복호화처리가 되지 않는다. ExtractAll로 직접구현
      Zip.ExtractAll(DestPath);
      Zip.Close;

      Result := True;
    except
      //손상된 파일이거나 암호화된 파일, 암호틀림
      on E: Exception do raise Exception.Create(E.Message);
    end;
  finally
    Zip.Free;
  end;
end;

class procedure TZipUtil.ZipProgressEvent(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
const
  {$J+}
  PrevFileName: string = '';
  {$J-}
begin
  if Assigned(FZipProgressProc) then
    if PrevFileName <> FileName then
    begin
      Inc(FZipCount);
      PrevFileName := FileName;
      FZipProgressProc(Sender, FileName, FTotalCount, FZipCount);
    end;
end;

{ TPKWAREZipCryptor }

function TPKWAREZipCryptor.CalcDecryptByte: Byte;
var
  temp: UInt64;
begin
  temp := FKey[2] or 2;
  Result := word(temp * (temp xor 1)) shr 8;
end;

procedure TPKWAREZipCryptor.Decrypt(var Buffer: TBytes);
begin
  for var I := Low(Buffer) to High(Buffer) do
    DecryptByte(Buffer[I]);
end;

procedure TPKWAREZipCryptor.InitKeys(const APassword: string);
var
  B: Byte;
begin
  FKey[0] := 305419896;
  FKey[1] := 591751049;
  FKey[2] := 878082192;
  for B in TEncoding.ANSI.GetBytes(APassword) do begin
    UpdateKeys(B);
  end;
end;

procedure TPKWAREZipCryptor.DecryptByte(var Value: Byte);
begin
  Value := Value xor CalcDecryptByte;
  UpdateKeys(Value);
end;

procedure TPKWAREZipCryptor.Encrypt(var Buffer: TBytes);
begin
  for var I := Low(Buffer) to High(Buffer) do
    EncryptByte(Buffer[I]);
end;

procedure TPKWAREZipCryptor.EncryptByte(var Value: Byte);
var
  temp: Byte;
begin
  temp := CalcDecryptByte;
  UpdateKeys(Value);
  Value := Value xor temp;
end;

procedure TPKWAREZipCryptor.Init(const APassword: string; AEncrypt: Boolean);
begin
  InitKeys(APassword);
end;

function TPKWAREZipCryptor.UpdateCRC32(Key: LongWord; Value: Byte): LongWord;
begin
  { Source: http://www.swissdelphicenter.com/de/showcode.php?id=268 }
  Result := (Key shr 8) xor CRC32_TABLE[Value xor (Key and $000000FF)];
end;

procedure TPKWAREZipCryptor.UpdateKeys(Value: Byte);
begin
  FKey[0] := UpdateCRC32(FKey[0], Value);
  FKey[1] := FKey[1] + (FKey[0] and $000000FF);
  FKey[1] := longword(FKey[1] * 134775813 + 1);
  FKey[2] := UpdateCRC32(FKey[2], Byte(FKey[1] shr 24));
end;

end.

댓글