unit JclZLib;

const
  JclZLibStreamDefaultBufferSize = 32 * 1024;

const
  {$IFDEF MSWINDOWS}
  JclZLibDefaultLineSeparator = #$0D#$0A;
  {$ENDIF MSWINDOWS}
  {$IFDEF UNIX}
  JclZLibDefaultLineSeparator = #$0A;
  {$ENDIF UNIX}

const
  WindowsPathDelimiter = '\';
  UnixPathDelimiter = '/';
  {$IFNDEF RTL140_UP}
  {$IFDEF MSWINDOWS}
  PathDelim = WindowsPathDelimiter;
  {$ENDIF MSWINDOWS}
  {$IFDEF UNIX}
  PathDelim = UnixPathDelimiter;
  {$ENDIF UNIX}
  {$ENDIF ~RTL140_UP}

//--------------------------------------------------------------------------------------------------
// zlib format support
//--------------------------------------------------------------------------------------------------

type
  TJclZLibStream = class(TStream)
  protected
    FStream: TStream;
    FBufferSize: Integer;
    FBuffer: Pointer;
    FZLibStream: TZStreamRec;
    procedure SetSize(NewSize: Longint); override;
  public
    constructor Create(const Stream: TStream; const BufferSize: Integer);
    destructor Destroy; override;
    function Seek(Offset: Longint; Origin: Word): Longint; override;
  end;

  TJclZLibReader = class(TJclZLibStream)
  protected
    FEndOfStream: Boolean;
    procedure ReadNextBlock;
    procedure FinishZLibStream;
  public
    constructor Create(const Stream: TStream;
      const BufferSize: Integer = JclZLibStreamDefaultBufferSize;
      const WindowBits: Integer = DEF_WBITS);

    destructor Destroy; override;

    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Seek(Offset: Longint; Origin: Word): Longint; override;

    procedure Reset;
    procedure SyncZLibStream;

    property EndOfStream: Boolean read FEndOfStream;
  end;

  TJclZLibWriter = class(TJclZLibStream)
  protected
    procedure WriteNextBlock;
    procedure FlushZLibStream(const Flush: Integer);
  public
    constructor Create(const Stream: TStream;
      const BufferSize: Integer = JclZLibStreamDefaultBufferSize;
      const Level: Integer = Z_DEFAULT_COMPRESSION;
      const Strategy: Integer = Z_DEFAULT_STRATEGY;
      const WindowBits: Integer = DEF_WBITS);

    destructor Destroy; override;

    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Seek(Offset: Longint; Origin: Word): Longint; override;

    procedure Reset;
  end;

  EJclZLibError = class(EJclError);

// zlib error texts
function GetZLibErrorText(const ErrorCode: Integer): PResStringRec;

function ZLibCompressMem(const Src: Pointer; SrcLen: Integer;
  out Dst: Pointer; out DstLen: Integer; out DstCapacity: Integer;
  const Level: Integer = Z_DEFAULT_COMPRESSION): Boolean;

// Flush:
//   Z_SYNC_FLUSH:  DstCapacity can be 0
//   Z_FINISH:      decompress with faster routine in a single step
//                  DstCapacity must be >= uncompressed size
function ZLibDecompressMem(const Src: Pointer; SrcLen: Integer;
  out Dst: Pointer; out DstLen: Integer; var DstCapacity: Integer;
  const Flush: Integer = Z_SYNC_FLUSH): Boolean;

type
  TJclGZipStream = class(TStream)
  protected
    FStream: TStream;
    FCRC32: LongWord;
    FUncompressedSize: LongWord;
    procedure SetSize(NewSize: Longint); override;
  public
    constructor Create(const Stream: TStream);
    destructor Destroy; override;
    function Seek(Offset: Longint; Origin: Word): Longint; override;
  end;

  TJclGZipReader = class(TJclGZipStream)
  private
    FZLibReader: TJclZLibReader;
    FTextMode: Boolean;
    FFilename: string;
    FComment: string;
    FTimeStamp: TJclUnixTime32;
    FLevel: Integer;
    FOperatingSystem: Byte;
    FMultipartNumber: Word;
    FExtraField: Pointer;
    FExtraFieldSize: Integer;
    FEndOfStream: Boolean;
  public
    constructor Create(const Stream: TStream;
      const BufferSize: Integer = JclZLibStreamDefaultBufferSize;
      const LineSeparator: string = JclZLibDefaultLineSeparator);

    destructor Destroy; override;

    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;

    property TextMode: Boolean read FTextMode;
    property Filename: string read FFilename;
    property Comment: string read FComment;
    property TimeStamp: TJclUnixTime32 read FTimeStamp;
    property Level: Integer read FLevel;
    property OperatingSystem: Byte read FOperatingSystem;
    property MultipartNumber: Word read FMultipartNumber;  // 0 = first part
    property ExtraField: Pointer read FExtraField;
    property ExtraFieldSize: Integer read FExtraFieldSize;

    property EndOfStream: Boolean read FEndOfStream;
  end;

  TJclGZipWriter = class(TJclGZipStream)
  private
    FTextMode: Boolean;
    FZLibWriter: TJclZLibWriter;
  public
    constructor Create(const Stream: TStream;
      const BufferSize: Integer = JclZLibStreamDefaultBufferSize;
      const Level: Integer = Z_DEFAULT_COMPRESSION;
      const Strategie: Integer = Z_DEFAULT_STRATEGY;
      const Filename: string = '';
      const TimeStamp: TJclUnixTime32 = 0;
      const Comment: string = '';
      const TextMode: Boolean = False;
      const ExtraField: Pointer = nil;
      const ExtraFieldSize: Integer = 0);

    destructor Destroy; override;

    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
  end;

  EJclGZipError = class(EJclError);

// gzip file extension
const
  JclGZipDefaultFileExtension = '.gz';

// if DstFilename = '' -> DstFilename := SrcFilename + JclGZipDefaultFileExtension
procedure GZipCompressFile(const SrcFilename: string; DstFilename: string;
  const Level: Integer = Z_DEFAULT_COMPRESSION);
procedure GZipDecompressFile(const SrcFilename: string; DstFilename: string);

const
  TarBlockSize = 512;

type
  TTarArchiveFormat = (
    tafDefaultFormat,    // format to be decided later
    tafV7Format,         // old V7 tar format
    tafOldGnuFormat,     // GNU format as per before tar 1.12
    tafPosixFormat,      // restricted, pure POSIX format
    tafGnuFormat);       // POSIX format with GNU extensions

type
  PSparse = ^TSparse;
  TSparse = packed record                 // offset
    Offset: array [0..11] of AnsiChar;    //  $00
    NumBytes: array [0..11] of AnsiChar;  //  $0C
  end;                                    //  $18

  PTarHeader = ^TTarHeader;
  TTarHeader = packed record       // offset
  case Integer of
    0: (Buffer: array [0..TarBlockSize - 1] of Byte);
    1: (
      // Old UNIX TAR format
      Name: array [0..99] of AnsiChar;          //  $000     Char + #0   / mit 0 gefüllt
      Mode: array [0..7] of AnsiChar;           //  $064     Octal + ' '#0   9 + 3 bits              20 34 30 37 35 35 20 00
      UID: array [0..7] of AnsiChar;            //  $06C     Octal + ' '#0   ignore on DOS           20 20 31 37 35 36 20 00
      GID: array [0..7] of AnsiChar;            //  $074     Octal + ' '#0   ignore on DOS           20 20 20 31 34 34 20 00
      Size: array [0..11] of AnsiChar;          //  $07C     Octal + ' '     size in bytes           20 20 20 20 20 20 20 20 20 20 30 20
      MTime: array [0..11] of AnsiChar;         //  $088     Octal + ' '     last modify Unix        20 36 37 32 32 34 34 36 31 30 37 20
      Chksum: array [0..7] of AnsiChar;         //  $094     Octal + ' '#0   >= 17 bit, init 0, add  20 20 37 35 37 32 00 20
      TypeFlag: AnsiChar;                       //  $09C     Octal           + ' '#0 ??              35
      Linkname: array [0..99] of AnsiChar;      //  $09D     Char + #0
      // Extension of POSIX P1003.1
      Magic: array [0..5] of AnsiChar;          //  $101     Char + #0                               75 73 74 61 72 20
      Version: array [0..1] of AnsiChar;        //  $107     Octal + ' '                             20 00
      UName: array [0..31] of AnsiChar;         //  $109     Char + #0                               72 63 64 00 ...
      GName: array [0..31] of AnsiChar;         //  $129     Char + #0                               75 73 65 72 73 00 ...
      DevMajor: array [0..7] of AnsiChar;       //  $149     Octal + ' '#0
      DevMinor: array [0..7] of AnsiChar;       //  $151     Octal + ' '#0
    case TTarArchiveFormat of
      tafV7Format: (
        FillV7: array [0..166] of AnsiChar);    //  $159
      tafPosixFormat: (
        Prefix: array [0..154] of AnsiChar;     //  $159         Prefix for name
        FillPosix: array [0..11] of AnsiChar);  //  $1F4
      tafOldGnuFormat: (
        ATime: array [0..11] of AnsiChar;       //  $159
        CTime: array [0..11] of AnsiChar;       //  $165
        Offset: array [0..11] of AnsiChar;      //  $171
        Longnames: array [0..3] of AnsiChar;    //  $17D
        Pad: AnsiChar;                          //  $181
        Sparses: array [0..3] of TSparse;       //  $182
        IsExtended: AnsiChar;                   //  $1E2
        RealSize: array [0..11] of AnsiChar;    //  $1E3
        FillGnu: array [0..16] of AnsiChar));   //  $1EF
  end;                                          //  $200

// ModeFlag Flags
type
  TTarMode = (
    tmOtherExec,   // execute/search by other
    tmOtherWrite,  // write by other
    tmOtherRead,   // read by other
    tmGroupExec,   // execute/search by group
    tmGroupWrite,  // write by group
    tmGroupRead,   // read by group
    tmOwnerExec,   // execute/search by owner
    tmOwnerWrite,  // write by owner
    tmOwnerRead,   // read by owner
    tmSaveText,    // reserved
    tmSetGID,      // set GID on execution
    tmSetUID);     // set UID on execution
  TTarModes = set of TTarMode;

// TypeFlag
type
  TTarTypeFlag = AnsiChar;

const                     //                     V7  Posix
  ttfRegFile      = '0';  // regular file         x    x
  ttfARegFile     = #0;   // regular file         x    x
  ttfLink         = '1';  // link                 x    x
  ttfSymbolicLink = '2';  // symbolic link        x
  ttfCharacter    = '3';  // character special         x
  ttfBlock        = '4';  // block special             x
  ttfDirectory    = '5';  // directory                 x
  ttfFIFO         = '6';  // FIFO special              x
  ttfContiguous   = '7';  // contiguous file

  // GNU extensions
  ttfGnuDumpDir   = 'D';
  ttfGnuLongLink  = 'K';  // next file have a long link name
  ttfGnuLongName  = 'L';  // next file have a long name
  ttfGnuMultiVol  = 'M';  // file began on another volume
  ttfGnuNames     = 'N';  // long filename
  ttfGnuSparse    = 'S';  // sparse files
  ttfGnuVolHeader = 'V';  // Volume label (must be the first file)

const
  TarOldGnuMagic = 'ustar  '#0;  // old GNU  Magic + Version
  TarPosixMagic  = 'ustar'#0;    // Posix or GNU
  TarGnuVersion  = '00';

  // other version for GNU-Magic:  'GNUtar '#0

type
  TJclTarFileType = (tftUnknown, tftEof, tftFile, tftDirectory);
  
  TJclTarFileSize = Int64;
  

  TJclTarReader = class(TObject)
  private
    function GetFileDateTime: TDateTime;
  protected
    FTarStream: TStream;
    FHeader: TTarHeader;
    FArchiveFormat: TTarArchiveFormat;
    FFileType: TJclTarFileType;
    FFilename: string;
    FFileSize: TJclTarFileSize;
    FFileTime: TJclUnixTime32;
    function ReadHeader: Boolean;  // False if Eof
    procedure ScanHeader;
  public
    constructor Create(const TarStream: TStream);
    procedure CopyToStream(const FileStream: TStream; CanSeek: Boolean = False);
    procedure CopyToFile(const FilePath: string);
    procedure SkipFile;
    procedure SkipFileSeek;
    property FileType: TJclTarFileType read FFileType;
    property Filename: string read FFilename;
    property FileSize: TJclTarFileSize read FFileSize;
    property FileTime: TJclUnixTime32 read FFileTime;
    property FileDateTime: TDateTime read GetFileDateTime;
  end;

  TJclTarWriter = class(TObject)
  protected
    FTarStream: TStream;
    procedure AddEof;
  public
    constructor Create(const TarStream: TStream);
    destructor Destroy; override;
    procedure AddFile(FileRoot, Filename: string);
    procedure AddStream(const Stream: TStream; Filename: string;
      FileSize: TJclTarFileSize; FileTime: TJclUnixTime32);
    procedure AddDirectory(DirName: string);
  end;

  EJclTarError = class(EJclError);

procedure TarAllFiles(const TarFilename, FileRoot: string);
procedure TarFileList(const TarFilename, FileRoot: string; List: TStrings);
procedure TarFileArray(const TarFilename, FileRoot: string; const Filenames: array of string);
procedure TarGZipAllFiles(const TgzFilename, FileRoot: string);
procedure TarGZipFileList(const TgzFilename, FileRoot: string; List: TStrings);
procedure TarGZipFileArray(const TgzFilename, FileRoot: string; const Filenames: array of string);

procedure UnTarAllFiles(const TarFilename: string; DstDir: string);
procedure UnGZipTarAllFiles(const TgzFilename: string; DstDir: string);

procedure GetFileList(RootDir: string; List: TStrings);

