1291 lines
36 KiB
ObjectPascal
1291 lines
36 KiB
ObjectPascal
(* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is TurboPower Abbrevia
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* TurboPower Software
|
|
*
|
|
* Portions created by the Initial Developer are Copyright (C) 1997-2002
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Craig Peterson <capeterson@users.sourceforge.net>
|
|
*
|
|
* ***** END LICENSE BLOCK ***** *)
|
|
|
|
{*********************************************************}
|
|
{* ABBREVIA: AbGzTyp.pas *}
|
|
{*********************************************************}
|
|
{* ABBREVIA: TAbGzipArchive, TAbGzipItem classes *}
|
|
{*********************************************************}
|
|
{* Misc. constants, types, and routines for working *}
|
|
{* with GZip files *}
|
|
{* See: RFC 1952 *}
|
|
{* "GZIP file format specification version 4.3" *}
|
|
{* for more information on GZip *}
|
|
{* See "algorithm.doc" in Gzip source and "format.txt" *}
|
|
{* on gzip.org for differences from RFC *}
|
|
{*********************************************************}
|
|
|
|
unit AbGzTyp;
|
|
|
|
{$I AbDefine.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, AbUtils, AbArcTyp, AbTarTyp, AbVMStrm;
|
|
|
|
type
|
|
{ pre-defined "operating system" (really more FILE system)
|
|
types for the Gzip header }
|
|
TAbGzFileSystem =
|
|
(osFat, osAmiga, osVMS, osUnix, osVM_CMS, osAtariTOS,
|
|
osHPFS, osMacintosh, osZSystem, osCP_M, osTOPS20,
|
|
osNTFS, osQDOS, osAcornRISCOS, osVFAT, osMVS, osBeOS,
|
|
osTandem, osTHEOS, osUnknown, osUndefined);
|
|
|
|
type
|
|
PAbGzHeader = ^TAbGzHeader;
|
|
TAbGzHeader = packed record { SizeOf(TGzHeader) = 10}
|
|
ID1 : Byte; { ID Byte, should always be $1F}
|
|
ID2 : Byte; { ID Byte, should always be $8B}
|
|
CompMethod : Byte; { compression method used}
|
|
{ 0..7 reserved, 8 = deflate, others undefined as of this writing (4/27/2001)}
|
|
Flags : Byte; { misc flags}
|
|
{ Bit 0: FTEXT compressed file contains text, can be used for}
|
|
{ cross platform line termination translation}
|
|
{ Bit 1: FCONTINUATION file is a continuation of a multi-part gzip file}
|
|
{ RFC 1952 says this is the header CRC16 flag, but gzip}
|
|
{ reserves it and won't extract the file if this is set}
|
|
{ header data includes part number after header record}
|
|
{ Bit 2: FEXTRA header data contains Extra Data, starts after part}
|
|
{ number (if any)}
|
|
{ Bit 3: FNAME header data contains FileName, null terminated}
|
|
{ string starting immediately after Extra Data (if any)}
|
|
{ RFC 1952 says this is ISO 8859-1 encoded, but gzip}
|
|
{ always uses the system encoding}
|
|
{ Bit 4: FCOMMENT header data contains Comment, null terminated string}
|
|
{ starting immediately after FileName (if any)}
|
|
{ Bit 5: FENCRYPTED file is encrypted using zip-1.9 encryption }
|
|
{ header data contains a 12-byte encryption header }
|
|
{ starting immediately after Comment. Documented in}
|
|
{ "algorithm.doc", but unsupported in gzip}
|
|
{ Bits 6..7 are undefined and reserved as of this writing (8/25/2009)}
|
|
ModTime : LongInt; { File Modification (Creation) time,}
|
|
{ UNIX cdate format}
|
|
XtraFlags : Byte; { additional flags}
|
|
{ XtraFlags = 2 -- Deflate compressor used maximum compression algorithm}
|
|
{ XtraFlags = 4 -- Deflate compressor used fastest algorithm}
|
|
OS : Byte; { Operating system that created file,}
|
|
{ see GZOsToStr routine for values}
|
|
end;
|
|
|
|
TAbGzTailRec = packed record
|
|
CRC32 : LongInt; { crc for uncompressed data }
|
|
ISize : LongWord; { size of uncompressed data }
|
|
end;
|
|
|
|
TAbGzExtraFieldSubID = array[0..1] of AnsiChar;
|
|
|
|
type
|
|
TAbGzipExtraField = class(TAbExtraField)
|
|
private
|
|
FGZHeader : PAbGzHeader;
|
|
function GetID(aIndex : Integer): TAbGzExtraFieldSubID;
|
|
protected
|
|
procedure Changed; override;
|
|
public
|
|
constructor Create(aGZHeader : PAbGzHeader);
|
|
procedure Delete(aID : TAbGzExtraFieldSubID);
|
|
function Get(aID : TAbGzExtraFieldSubID;
|
|
out aData : Pointer; out aDataSize : Word) : Boolean;
|
|
procedure Put(aID : TAbGzExtraFieldSubID; const aData; aDataSize : Word);
|
|
public
|
|
property IDs[aIndex : Integer]: TAbGzExtraFieldSubID
|
|
read GetID;
|
|
end;
|
|
|
|
TAbGzipItem = class(TAbArchiveItem)
|
|
protected {private}
|
|
FGZHeader : TAbGzHeader;
|
|
FExtraField : TAbGzipExtraField;
|
|
FFileComment : AnsiString;
|
|
FRawFileName : AnsiString;
|
|
|
|
protected
|
|
function GetFileSystem: TAbGzFileSystem;
|
|
function GetHasExtraField: Boolean;
|
|
function GetHasFileComment: Boolean;
|
|
function GetHasFileName: Boolean;
|
|
function GetIsText: Boolean;
|
|
|
|
procedure SetFileComment(const Value : AnsiString);
|
|
procedure SetFileSystem(const Value: TAbGzFileSystem);
|
|
procedure SetIsText(const Value: Boolean);
|
|
|
|
function GetExternalFileAttributes : LongWord; override;
|
|
function GetIsEncrypted : Boolean; override;
|
|
function GetLastModFileDate : Word; override;
|
|
function GetLastModFileTime : Word; override;
|
|
function GetLastModTimeAsDateTime: TDateTime; override;
|
|
|
|
procedure SetExternalFileAttributes( Value : LongWord ); override;
|
|
procedure SetFileName(const Value : string); override;
|
|
procedure SetIsEncrypted(Value : Boolean); override;
|
|
procedure SetLastModFileDate(const Value : Word); override;
|
|
procedure SetLastModFileTime(const Value : Word); override;
|
|
procedure SetLastModTimeAsDateTime(const Value: TDateTime); override;
|
|
|
|
procedure SaveGzHeaderToStream(AStream : TStream);
|
|
procedure LoadGzHeaderFromStream(AStream : TStream);
|
|
public
|
|
property CompressionMethod : Byte
|
|
read FGZHeader.CompMethod;
|
|
|
|
property ExtraFlags : Byte {Default: 2}
|
|
read FGZHeader.XtraFlags write FGZHeader.XtraFlags;
|
|
|
|
property Flags : Byte
|
|
read FGZHeader.Flags;
|
|
|
|
property FileComment : AnsiString
|
|
read FFileComment write SetFileComment;
|
|
|
|
property FileSystem : TAbGzFileSystem {Default: osFat (Windows); osUnix (Linux)}
|
|
read GetFileSystem write SetFileSystem;
|
|
|
|
property ExtraField : TAbGzipExtraField
|
|
read FExtraField;
|
|
|
|
property IsEncrypted : Boolean
|
|
read GetIsEncrypted;
|
|
|
|
property HasExtraField : Boolean
|
|
read GetHasExtraField;
|
|
|
|
property HasFileName : Boolean
|
|
read GetHasFileName;
|
|
|
|
property HasFileComment : Boolean
|
|
read GetHasFileComment;
|
|
|
|
property IsText : Boolean
|
|
read GetIsText write SetIsText;
|
|
|
|
property GZHeader : TAbGzHeader
|
|
read FGZHeader;
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
TAbGzipStreamHelper = class(TAbArchiveStreamHelper)
|
|
private
|
|
function GetGzCRC: LongInt;
|
|
function GetFileSize: LongInt;
|
|
protected {private}
|
|
FItem : TAbGzipItem;
|
|
FTail : TAbGzTailRec;
|
|
public
|
|
constructor Create(AStream : TStream);
|
|
destructor Destroy; override;
|
|
|
|
procedure ExtractItemData(AStream : TStream); override;
|
|
function FindFirstItem : Boolean; override;
|
|
function FindNextItem : Boolean; override;
|
|
function SeekItem(Index : Integer): Boolean; override;
|
|
procedure SeekToItemData;
|
|
procedure WriteArchiveHeader; override;
|
|
procedure WriteArchiveItem(AStream : TStream); override;
|
|
procedure WriteArchiveTail; override;
|
|
function GetItemCount : Integer; override;
|
|
procedure ReadHeader; override;
|
|
procedure ReadTail; override;
|
|
|
|
property CRC : LongInt
|
|
read GetGzCRC;
|
|
property FileSize : LongInt
|
|
read GetFileSize;
|
|
property TailCRC : LongInt
|
|
read FTail.CRC32;
|
|
property TailSize : LongWord
|
|
read FTail.ISize;
|
|
end;
|
|
|
|
TAbGzipArchiveState = (gsGzip, gsTar);
|
|
|
|
TAbGzipArchive = class(TAbTarArchive)
|
|
private
|
|
FGZStream : TStream; { stream for GZip file}
|
|
FGZItem : TAbArchiveList; { item in Gzip (only one, but need polymorphism of class)}
|
|
FTarStream : TAbVirtualMemoryStream; { stream for possible contained Tar }
|
|
FTarList : TAbArchiveList; { items in possible contained Tar }
|
|
FTarAutoHandle: Boolean;
|
|
FState : TAbGzipArchiveState;
|
|
FIsGzippedTar : Boolean;
|
|
|
|
procedure SetTarAutoHandle(const Value: Boolean);
|
|
function GetIsGzippedTar: Boolean;
|
|
procedure SwapToGzip;
|
|
procedure SwapToTar;
|
|
|
|
protected
|
|
function CreateItem(const FileSpec : string): TAbArchiveItem;
|
|
override;
|
|
procedure ExtractItemAt(Index : Integer; const UseName : string);
|
|
override;
|
|
procedure ExtractItemToStreamAt(Index : Integer; aStream : TStream);
|
|
override;
|
|
procedure LoadArchive;
|
|
override;
|
|
procedure SaveArchive;
|
|
override;
|
|
procedure TestItemAt(Index : Integer);
|
|
override;
|
|
function FixName(const Value : string) : string;
|
|
override;
|
|
function GetSupportsEmptyFolders : Boolean;
|
|
override;
|
|
|
|
function GetItem(Index: Integer): TAbGzipItem;
|
|
procedure PutItem(Index: Integer; const Value: TAbGzipItem);
|
|
public {methods}
|
|
constructor CreateFromStream(aStream : TStream; const aArchiveName : string);
|
|
override;
|
|
destructor Destroy;
|
|
override;
|
|
|
|
procedure DoSpanningMediaRequest(Sender : TObject; ImageNumber : Integer;
|
|
var ImageName : string; var Abort : Boolean); override;
|
|
|
|
property TarAutoHandle : Boolean
|
|
read FTarAutoHandle write SetTarAutoHandle;
|
|
|
|
property IsGzippedTar : Boolean
|
|
read GetIsGzippedTar write FIsGzippedTar;
|
|
|
|
property Items[Index : Integer] : TAbGzipItem
|
|
read GetItem
|
|
write PutItem; default;
|
|
end;
|
|
|
|
function VerifyGZip(Strm : TStream) : TAbArchiveType;
|
|
function GZOsToStr(OS: Byte) : string;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFDEF MSWINDOWS}
|
|
Windows,
|
|
{$ENDIF}
|
|
{$IFDEF HasAnsiStrings}
|
|
System.AnsiStrings,
|
|
{$ENDIF}
|
|
SysUtils, AbBitBkt, AbCharset, AbDfBase, AbDfDec, AbDfEnc, AbExcept, AbResString;
|
|
|
|
const
|
|
{ Header Signature Values}
|
|
AB_GZ_HDR_ID1 = $1F;
|
|
AB_GZ_HDR_ID2 = $8B;
|
|
|
|
{ Test bits for TGzHeader.Flags field }
|
|
AB_GZ_FLAG_FTEXT = $01;
|
|
AB_GZ_FLAG_FCONTINUATION = $02;
|
|
AB_GZ_FLAG_FEXTRA = $04;
|
|
AB_GZ_FLAG_FNAME = $08;
|
|
AB_GZ_FLAG_FCOMMENT = $10;
|
|
AB_GZ_FLAG_FENCRYPTED = $20;
|
|
AB_GZ_UNSUPPORTED_FLAGS = $E2;
|
|
|
|
{ GZip OS source flags }
|
|
AB_GZ_OS_ID_FAT = 0;
|
|
AB_GZ_OS_ID_Amiga = 1;
|
|
AB_GZ_OS_ID_VMS = 2;
|
|
AB_GZ_OS_ID_Unix = 3;
|
|
AB_GZ_OS_ID_VM_CMS = 4;
|
|
AB_GZ_OS_ID_AtariTOS = 5;
|
|
AB_GZ_OS_ID_HPFS = 6;
|
|
AB_GZ_OS_ID_Macintosh = 7;
|
|
AB_GZ_OS_ID_Z_System = 8;
|
|
AB_GZ_OS_ID_CP_M = 9;
|
|
AB_GZ_OS_ID_TOPS20 = 10;
|
|
AB_GZ_OS_ID_NTFS = 11;
|
|
AB_GZ_OS_ID_QDOS = 12;
|
|
AB_GZ_OS_ID_AcornRISCOS = 13;
|
|
AB_GZ_OS_ID_VFAT = 14;
|
|
AB_GZ_OS_ID_MVS = 15;
|
|
AB_GZ_OS_ID_BEOS = 16;
|
|
AB_GZ_OS_ID_TANDEM = 17;
|
|
AB_GZ_OS_ID_THEOS = 18;
|
|
AB_GZ_OS_ID_unknown = 255;
|
|
|
|
function GZOsToStr(OS: Byte) : string;
|
|
{
|
|
Return a descriptive string for TGzHeader.OS field
|
|
}
|
|
begin
|
|
case OS of
|
|
AB_GZ_OS_ID_FAT : Result := AbGzOsFat;
|
|
AB_GZ_OS_ID_Amiga : Result := AbGzOsAmiga;
|
|
AB_GZ_OS_ID_VMS : Result := AbGzOsVMS;
|
|
AB_GZ_OS_ID_Unix : Result := AbGzOsUnix;
|
|
AB_GZ_OS_ID_VM_CMS : Result := AbGzOsVM_CMS;
|
|
AB_GZ_OS_ID_AtariTOS : Result := AbGzOsAtari;
|
|
AB_GZ_OS_ID_HPFS : Result := AbGzOsHPFS;
|
|
AB_GZ_OS_ID_Macintosh : Result := AbGzOsMacintosh;
|
|
AB_GZ_OS_ID_Z_System : Result := AbGzOsZ_System;
|
|
AB_GZ_OS_ID_CP_M : Result := AbGzOsCP_M;
|
|
AB_GZ_OS_ID_TOPS20 : Result := AbGzOsTOPS_20;
|
|
AB_GZ_OS_ID_NTFS : Result := AbGzOsNTFS;
|
|
AB_GZ_OS_ID_QDOS : Result := AbGzOsQDOS;
|
|
AB_GZ_OS_ID_AcornRISCOS : Result := AbGzOsAcornRISCOS;
|
|
AB_GZ_OS_ID_VFAT : Result := AbGzOsVFAT;
|
|
AB_GZ_OS_ID_MVS : Result := AbGzOsMVS;
|
|
AB_GZ_OS_ID_BEOS : Result := AbGzOsBeOS;
|
|
AB_GZ_OS_ID_TANDEM : Result := AbGzOsTandem;
|
|
AB_GZ_OS_ID_THEOS : Result := AbGzOsTHEOS;
|
|
AB_GZ_OS_ID_unknown : Result := AbGzOsunknown;
|
|
else
|
|
Result := AbGzOsUndefined;
|
|
end;
|
|
end;
|
|
|
|
|
|
function VerifyHeader(const Header : TAbGzHeader) : Boolean;
|
|
begin
|
|
{ check id fields and if deflated (only handle deflate anyway)}
|
|
Result := (Header.ID1 = AB_GZ_HDR_ID1) and
|
|
(Header.ID2 = AB_GZ_HDR_ID2) and
|
|
(Header.CompMethod = 8 {deflate});
|
|
end;
|
|
|
|
function VerifyGZip(Strm : TStream) : TAbArchiveType;
|
|
var
|
|
GHlp : TAbGzipStreamHelper;
|
|
Hlpr : TAbDeflateHelper;
|
|
PartialTarData : TMemoryStream;
|
|
CurPos : Int64;
|
|
begin
|
|
Result := atUnknown;
|
|
CurPos := Strm.Position;
|
|
try
|
|
Strm.Seek(0, soBeginning);
|
|
|
|
{prepare for the try..finally}
|
|
Hlpr := nil;
|
|
PartialTarData := nil;
|
|
|
|
GHlp := TAbGzipStreamHelper.Create(Strm);
|
|
try
|
|
{create the stream helper and read the item header}
|
|
GHlp.ReadHeader;
|
|
|
|
{ check id fields and if deflated (only handle deflate anyway)}
|
|
if VerifyHeader(GHlp.FItem.FGZHeader) then begin
|
|
Result := atGZip; { provisional }
|
|
|
|
{ check if is actually a Gzipped Tar }
|
|
{ partial extract contents, verify vs. Tar }
|
|
PartialTarData := TMemoryStream.Create;
|
|
GHlp.SeekToItemData;
|
|
Hlpr := TAbDeflateHelper.Create;
|
|
Hlpr.PartialSize := 512;
|
|
PartialTarData.SetSize(512 * 2);
|
|
Inflate(Strm, PartialTarData, Hlpr);
|
|
|
|
{set to beginning of extracted data}
|
|
PartialTarData.Position := 0;
|
|
|
|
if (VerifyTar(PartialTarData) = atTar) then
|
|
Result := atGZippedTar;
|
|
end;
|
|
finally
|
|
GHlp.Free;
|
|
Hlpr.Free;
|
|
PartialTarData.Free;
|
|
end;
|
|
except
|
|
on EReadError do
|
|
Result := atUnknown;
|
|
end;
|
|
Strm.Position := CurPos;
|
|
end;
|
|
|
|
{ TAbGzipExtraField }
|
|
|
|
constructor TAbGzipExtraField.Create(aGZHeader : PAbGzHeader);
|
|
begin
|
|
inherited Create;
|
|
FGZHeader := aGZHeader;
|
|
end;
|
|
|
|
procedure TAbGzipExtraField.Changed;
|
|
begin
|
|
if Buffer = nil then
|
|
FGzHeader.Flags := FGzHeader.Flags and not AB_GZ_FLAG_FEXTRA
|
|
else
|
|
FGzHeader.Flags := FGzHeader.Flags or AB_GZ_FLAG_FEXTRA;
|
|
end;
|
|
|
|
procedure TAbGzipExtraField.Delete(aID : TAbGzExtraFieldSubID);
|
|
begin
|
|
inherited Delete(Word(aID));
|
|
end;
|
|
|
|
function TAbGzipExtraField.GetID(aIndex : Integer): TAbGzExtraFieldSubID;
|
|
begin
|
|
Result := TAbGzExtraFieldSubID(inherited IDs[aIndex]);
|
|
end;
|
|
|
|
function TAbGzipExtraField.Get(aID : TAbGzExtraFieldSubID; out aData : Pointer;
|
|
out aDataSize : Word) : Boolean;
|
|
begin
|
|
Result := inherited Get(Word(aID), aData, aDataSize);
|
|
end;
|
|
|
|
procedure TAbGzipExtraField.Put(aID : TAbGzExtraFieldSubID; const aData; aDataSize : Word);
|
|
begin
|
|
inherited Put(Word(aID), aData, aDataSize);
|
|
end;
|
|
|
|
|
|
{ TAbGzipStreamHelper }
|
|
|
|
constructor TAbGzipStreamHelper.Create(AStream : TStream);
|
|
begin
|
|
inherited Create(AStream);
|
|
FItem := TAbGzipItem.Create;
|
|
end;
|
|
|
|
destructor TAbGzipStreamHelper.Destroy;
|
|
begin
|
|
FItem.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function ReadCStringInStream(AStream: TStream): AnsiString;
|
|
{
|
|
locate next instance of a null character in a stream
|
|
leaves stream positioned just past that,
|
|
or at end of stream if not found or null is last byte in stream.
|
|
Result is the entire read string.
|
|
}
|
|
const
|
|
BuffSiz = 1024;
|
|
var
|
|
Buff : array [0..BuffSiz-1] of AnsiChar;
|
|
Len, DataRead : LongInt;
|
|
begin
|
|
{ basically what this is supposed to do is...}
|
|
{
|
|
repeat
|
|
AStream.Read(C, 1);
|
|
Result := Result + C;
|
|
until (AStream.Position = AStream.Size) or (C = #0);
|
|
}
|
|
Result := '';
|
|
repeat
|
|
DataRead := AStream.Read(Buff, BuffSiz - 1);
|
|
Buff[DataRead] := #0;
|
|
Len := AbStrLen(Buff);
|
|
if Len > 0 then begin
|
|
SetLength(Result, Length(Result) + Len);
|
|
Move(Buff, Result[Length(Result) - Len + 1], Len);
|
|
end;
|
|
if Len < DataRead then begin
|
|
AStream.Seek(Len - DataRead + 1, soCurrent);
|
|
Break;
|
|
end;
|
|
until DataRead = 0;
|
|
end;
|
|
|
|
procedure TAbGzipStreamHelper.SeekToItemData;
|
|
{find end of header data, including FileName etc.}
|
|
begin
|
|
{** Seek to Compressed Data **}
|
|
FStream.Seek(0, soBeginning);
|
|
FItem.LoadGzHeaderFromStream(FStream);
|
|
end;
|
|
|
|
procedure TAbGzipStreamHelper.ExtractItemData(AStream: TStream);
|
|
var
|
|
Helper : TAbDeflateHelper;
|
|
begin
|
|
Helper := TAbDeflateHelper.Create;
|
|
try
|
|
SeekToItemData;
|
|
if (AStream is TAbBitBucketStream) then
|
|
Helper.Options := Helper.Options or dfc_TestOnly;
|
|
FItem.CRC32 := Inflate(FStream, AStream, Helper);
|
|
FItem.UncompressedSize := AStream.Size{Helper.NormalSize};
|
|
finally
|
|
Helper.Free;
|
|
end;
|
|
end;
|
|
|
|
function TAbGzipStreamHelper.FindFirstItem: Boolean;
|
|
var
|
|
GZH : TAbGzHeader;
|
|
DataRead : Integer;
|
|
begin
|
|
Result := False;
|
|
FStream.Seek(0, soBeginning);
|
|
DataRead := FStream.Read(GZH, SizeOf(TAbGzHeader));
|
|
if (DataRead = SizeOf(TAbGzHeader)) and VerifyHeader(GZH) then begin
|
|
FItem.FGZHeader := GZH;
|
|
Result := True;
|
|
end;
|
|
FStream.Seek(0, soBeginning);
|
|
end;
|
|
|
|
function TAbGzipStreamHelper.FindNextItem: Boolean;
|
|
begin
|
|
{ only one item in a GZip }
|
|
Result := False;
|
|
end;
|
|
|
|
function TAbGzipStreamHelper.SeekItem(Index: Integer): Boolean;
|
|
begin
|
|
if Index > 0 then
|
|
Result := False
|
|
else
|
|
Result := FindFirstItem;
|
|
end;
|
|
|
|
procedure TAbGzipStreamHelper.WriteArchiveHeader;
|
|
begin
|
|
FItem.SaveGzHeaderToStream(FStream);
|
|
end;
|
|
|
|
procedure TAbGzipStreamHelper.WriteArchiveItem(AStream: TStream);
|
|
var
|
|
Helper : TAbDeflateHelper;
|
|
begin
|
|
Helper := TAbDeflateHelper.Create;
|
|
try
|
|
FItem.CRC32 := Deflate(AStream, FStream, Helper);
|
|
FItem.UncompressedSize := AStream.Size;
|
|
finally
|
|
Helper.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TAbGzipStreamHelper.WriteArchiveTail;
|
|
var
|
|
Tail : TAbGzTailRec;
|
|
begin
|
|
Tail.CRC32 := FItem.CRC32;
|
|
Tail.ISize := FItem.UncompressedSize;
|
|
FStream.Write(Tail, SizeOf(TAbGzTailRec));
|
|
end;
|
|
|
|
function TAbGzipStreamHelper.GetItemCount: Integer;
|
|
begin
|
|
{ only one item in a gzip }
|
|
Result := 1;
|
|
end;
|
|
|
|
procedure TAbGzipStreamHelper.ReadHeader;
|
|
begin
|
|
FItem.LoadGzHeaderFromStream(FStream);
|
|
end;
|
|
|
|
procedure TAbGzipStreamHelper.ReadTail;
|
|
begin
|
|
FStream.Read(FTail, SizeOf(TAbGzTailRec));
|
|
end;
|
|
|
|
function TAbGzipStreamHelper.GetGzCRC: LongInt;
|
|
begin
|
|
Result := FItem.CRC32;
|
|
end;
|
|
|
|
function TAbGzipStreamHelper.GetFileSize: LongInt;
|
|
begin
|
|
Result := FItem.UncompressedSize;
|
|
end;
|
|
|
|
{ TAbGzipItem }
|
|
|
|
constructor TAbGzipItem.Create;
|
|
begin
|
|
inherited Create;
|
|
|
|
{ default ID fields }
|
|
FGzHeader.ID1 := AB_GZ_HDR_ID1;
|
|
FGzHeader.ID2 := AB_GZ_HDR_ID2;
|
|
|
|
{ compression method }
|
|
FGzHeader.CompMethod := 8; { deflate }
|
|
|
|
{ Maxium Compression }
|
|
FGzHeader.XtraFlags := 2;
|
|
|
|
FFileName := '';
|
|
FFileComment := '';
|
|
FExtraField := TAbGzipExtraField.Create(@FGzHeader);
|
|
|
|
{ source OS ID }
|
|
{$IFDEF LINUX } {assume EXT2 system }
|
|
FGzHeader.OS := AB_GZ_OS_ID_Unix;
|
|
{$ENDIF LINUX }
|
|
{$IFDEF MSWINDOWS } {assume FAT system }
|
|
FGzHeader.OS := AB_GZ_OS_ID_FAT;
|
|
{$ENDIF MSWINDOWS }
|
|
end;
|
|
|
|
destructor TAbGzipItem.Destroy;
|
|
begin
|
|
FExtraField.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TAbGzipItem.GetExternalFileAttributes: LongWord;
|
|
begin
|
|
{ GZip has no provision for storing attributes }
|
|
Result := 0;
|
|
end;
|
|
|
|
function TAbGzipItem.GetFileSystem: TAbGzFileSystem;
|
|
begin
|
|
case FGzHeader.OS of
|
|
0..18: Result := TAbGzFileSystem(FGzHeader.OS);
|
|
255: Result := osUnknown;
|
|
else
|
|
Result := osUndefined;
|
|
end; { case }
|
|
end;
|
|
|
|
function TAbGzipItem.GetIsEncrypted: Boolean;
|
|
begin
|
|
Result := (FGZHeader.Flags and AB_GZ_FLAG_FENCRYPTED) = AB_GZ_FLAG_FENCRYPTED;
|
|
end;
|
|
|
|
function TAbGzipItem.GetHasExtraField: Boolean;
|
|
begin
|
|
Result := (FGZHeader.Flags and AB_GZ_FLAG_FEXTRA) = AB_GZ_FLAG_FEXTRA;
|
|
end;
|
|
|
|
function TAbGzipItem.GetHasFileComment: Boolean;
|
|
begin
|
|
Result := (FGZHeader.Flags and AB_GZ_FLAG_FCOMMENT) = AB_GZ_FLAG_FCOMMENT;
|
|
end;
|
|
|
|
function TAbGzipItem.GetHasFileName: Boolean;
|
|
begin
|
|
Result := (FGZHeader.Flags and AB_GZ_FLAG_FNAME) = AB_GZ_FLAG_FNAME;
|
|
end;
|
|
|
|
function TAbGzipItem.GetIsText: Boolean;
|
|
begin
|
|
Result := (FGZHeader.Flags and AB_GZ_FLAG_FTEXT) = AB_GZ_FLAG_FTEXT;
|
|
end;
|
|
|
|
function TAbGzipItem.GetLastModFileDate: Word;
|
|
begin
|
|
{ convert to local DOS file Date }
|
|
Result := LongRec(AbDateTimeToDosFileDate(LastModTimeAsDateTime)).Hi;
|
|
end;
|
|
|
|
function TAbGzipItem.GetLastModFileTime: Word;
|
|
begin
|
|
{ convert to local DOS file Time }
|
|
Result := LongRec(AbDateTimeToDosFileDate(LastModTimeAsDateTime)).Lo;
|
|
end;
|
|
|
|
function TAbGzipItem.GetLastModTimeAsDateTime: TDateTime;
|
|
begin
|
|
Result := AbUnixTimeToLocalDateTime(FGZHeader.ModTime);
|
|
end;
|
|
|
|
procedure TAbGzipItem.LoadGzHeaderFromStream(AStream: TStream);
|
|
var
|
|
LenW : Word;
|
|
begin
|
|
AStream.Read(FGzHeader, SizeOf(TAbGzHeader));
|
|
if not VerifyHeader(FGzHeader) then
|
|
Exit;
|
|
|
|
{ Skip part number, if any }
|
|
if (FGzHeader.Flags and AB_GZ_FLAG_FCONTINUATION) = AB_GZ_FLAG_FCONTINUATION then
|
|
AStream.Seek(SizeOf(Word), soCurrent);
|
|
|
|
if HasExtraField then begin
|
|
{ get length of extra data }
|
|
AStream.Read(LenW, SizeOf(Word));
|
|
FExtraField.LoadFromStream(AStream, LenW);
|
|
end
|
|
else
|
|
FExtraField.Clear;
|
|
|
|
{ Get Filename, if any }
|
|
if HasFileName then begin
|
|
FRawFileName := ReadCStringInStream(AStream);
|
|
FFileName := AbRawBytesToString(FRawFileName)
|
|
end
|
|
else
|
|
FFileName := 'unknown';
|
|
|
|
{ any comment present? }
|
|
if HasFileComment then
|
|
FFileComment := ReadCStringInStream(AStream)
|
|
else
|
|
FFileComment := '';
|
|
|
|
|
|
{Assert: stream should now be located at start of compressed data }
|
|
{If file was compressed with 3.3 spec this will be invalid so use with care}
|
|
CompressedSize := AStream.Size - AStream.Position - SizeOf(TAbGzTailRec);
|
|
|
|
FDiskFileName := FileName;
|
|
AbUnfixName(FDiskFileName);
|
|
Action := aaNone;
|
|
Tagged := False;
|
|
end;
|
|
|
|
procedure TAbGzipItem.SaveGzHeaderToStream(AStream: TStream);
|
|
var
|
|
LenW : Word;
|
|
begin
|
|
{ default ID fields }
|
|
FGzHeader.ID1 := AB_GZ_HDR_ID1;
|
|
FGzHeader.ID2 := AB_GZ_HDR_ID2;
|
|
|
|
{ compression method }
|
|
FGzHeader.CompMethod := 8; { deflate }
|
|
|
|
{ reset unsupported flags }
|
|
FGzHeader.Flags := FGzHeader.Flags and not AB_GZ_UNSUPPORTED_FLAGS;
|
|
|
|
{ main header data }
|
|
AStream.Write(FGzHeader, SizeOf(TAbGzHeader));
|
|
|
|
{ add extra field if any }
|
|
if HasExtraField then begin
|
|
LenW := Length(FExtraField.Buffer);
|
|
AStream.Write(LenW, SizeOf(LenW));
|
|
if LenW > 0 then
|
|
AStream.Write(FExtraField.Buffer[0], LenW);
|
|
end;
|
|
|
|
{ add filename if any (and include final #0 from string) }
|
|
if HasFileName then
|
|
AStream.Write(FRawFileName[1], Length(FRawFileName) + 1);
|
|
|
|
{ add file comment if any (and include final #0 from string) }
|
|
if HasFileComment then
|
|
AStream.Write(FFileComment[1], Length(FFileComment) + 1);
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetExternalFileAttributes(Value: LongWord);
|
|
begin
|
|
{ do nothing }
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetFileComment(const Value: AnsiString);
|
|
begin
|
|
FFileComment := Value;
|
|
if FFileComment <> '' then
|
|
FGzHeader.Flags := FGzHeader.Flags or AB_GZ_FLAG_FCOMMENT
|
|
else
|
|
FGzHeader.Flags := FGzHeader.Flags and not AB_GZ_FLAG_FCOMMENT;
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetFileName(const Value: string);
|
|
begin
|
|
FFileName := Value;
|
|
FRawFileName := AbStringToUnixBytes(Value);
|
|
if Value <> '' then
|
|
FGzHeader.Flags := FGzHeader.Flags or AB_GZ_FLAG_FNAME
|
|
else
|
|
FGzHeader.Flags := FGzHeader.Flags and not AB_GZ_FLAG_FNAME;
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetFileSystem(const Value: TAbGzFileSystem);
|
|
begin
|
|
if Value = osUnknown then
|
|
FGzHeader.OS := 255
|
|
else
|
|
FGzHeader.OS := Ord(Value);
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetIsEncrypted(Value: Boolean);
|
|
begin
|
|
{ do nothing }
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetIsText(const Value: Boolean);
|
|
begin
|
|
if Value then
|
|
FGzHeader.Flags := FGzHeader.Flags or AB_GZ_FLAG_FTEXT
|
|
else
|
|
FGzHeader.Flags := FGzHeader.Flags and not AB_GZ_FLAG_FTEXT;
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetLastModFileDate(const Value: Word);
|
|
begin
|
|
{ replace date, keep existing time }
|
|
LastModTimeAsDateTime :=
|
|
EncodeDate(
|
|
Value shr 9 + 1980,
|
|
Value shr 5 and 15,
|
|
Value and 31) +
|
|
Frac(LastModTimeAsDateTime);
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetLastModFileTime(const Value: Word);
|
|
begin
|
|
{ keep current date, replace time }
|
|
LastModTimeAsDateTime :=
|
|
Trunc(LastModTimeAsDateTime) +
|
|
EncodeTime(
|
|
Value shr 11,
|
|
Value shr 5 and 63,
|
|
Value and 31 shl 1, 0);
|
|
end;
|
|
|
|
procedure TAbGzipItem.SetLastModTimeAsDateTime(const Value: TDateTime);
|
|
begin
|
|
FGZHeader.ModTime := AbLocalDateTimeToUnixTime(Value);
|
|
end;
|
|
|
|
{ TAbGzipArchive }
|
|
|
|
constructor TAbGzipArchive.CreateFromStream(aStream : TStream;
|
|
const aArchiveName : string);
|
|
begin
|
|
inherited CreateFromStream(aStream, aArchiveName);
|
|
FState := gsGzip;
|
|
FGZStream := FStream;
|
|
FGZItem := FItemList;
|
|
FTarStream := TAbVirtualMemoryStream.Create;
|
|
FTarList := TAbArchiveList.Create(True);
|
|
end;
|
|
|
|
procedure TAbGzipArchive.SwapToTar;
|
|
begin
|
|
FStream := FTarStream;
|
|
FItemList := FTarList;
|
|
FState := gsTar;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.SwapToGzip;
|
|
begin
|
|
FStream := FGzStream;
|
|
FItemList := FGzItem;
|
|
FState := gsGzip;
|
|
end;
|
|
|
|
function TAbGzipArchive.CreateItem(const FileSpec: string): TAbArchiveItem;
|
|
var
|
|
GzItem : TAbGzipItem;
|
|
begin
|
|
if IsGZippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
Result := inherited CreateItem(FileSpec);
|
|
end
|
|
else begin
|
|
SwapToGzip;
|
|
GzItem := TAbGzipItem.Create;
|
|
try
|
|
GzItem.CompressedSize := 0;
|
|
GzItem.CRC32 := 0;
|
|
GzItem.DiskFileName := ExpandFileName(FileSpec);
|
|
GzItem.FileName := FixName(FileSpec);
|
|
Result := GzItem;
|
|
except
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TAbGzipArchive.Destroy;
|
|
begin
|
|
SwapToGzip;
|
|
FTarList.Free;
|
|
FTarStream.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
procedure TAbGzipArchive.ExtractItemAt(Index: Integer;
|
|
const UseName: string);
|
|
var
|
|
OutStream : TFileStream;
|
|
CurItem : TAbGzipItem;
|
|
begin
|
|
if IsGZippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
inherited ExtractItemAt(Index, UseName);
|
|
end
|
|
else begin
|
|
SwapToGzip;
|
|
if Index > 0 then Index := 0; { only one item in a GZip}
|
|
|
|
CurItem := TAbGzipItem(ItemList[Index]);
|
|
|
|
OutStream := TFileStream.Create(UseName, fmCreate or fmShareDenyNone);
|
|
try
|
|
try {OutStream}
|
|
ExtractItemToStreamAt(Index, OutStream);
|
|
finally {OutStream}
|
|
OutStream.Free;
|
|
end; {OutStream}
|
|
AbSetFileTime(UseName, CurItem.LastModTimeAsDateTime);
|
|
AbSetFileAttr(UseName, CurItem.NativeFileAttributes);
|
|
except
|
|
on E : EAbUserAbort do begin
|
|
FStatus := asInvalid;
|
|
if FileExists(UseName) then
|
|
DeleteFile(UseName);
|
|
raise;
|
|
end else begin
|
|
if FileExists(UseName) then
|
|
DeleteFile(UseName);
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.ExtractItemToStreamAt(Index: Integer;
|
|
aStream: TStream);
|
|
var
|
|
GzHelp : TAbGzipStreamHelper;
|
|
begin
|
|
if IsGzippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
inherited ExtractItemToStreamAt(Index, aStream);
|
|
end
|
|
else begin
|
|
SwapToGzip;
|
|
{ note Index ignored as there's only one item in a GZip }
|
|
|
|
GZHelp := TAbGzipStreamHelper.Create(FGzStream);
|
|
try
|
|
{ read GZip Header }
|
|
GzHelp.ReadHeader;
|
|
|
|
{ extract copy data from GZip}
|
|
GzHelp.ExtractItemData(aStream);
|
|
|
|
{ Get validation data }
|
|
GzHelp.ReadTail;
|
|
|
|
{$IFDEF STRICTGZIP}
|
|
{ According to
|
|
http://www.gzip.org/zlib/rfc1952.txt
|
|
|
|
A compliant gzip compressor should calculate and set the CRC32 and ISIZE.
|
|
However, a compliant decompressor should not check these values.
|
|
|
|
If you want to check the the values of the CRC32 and ISIZE in a GZIP file
|
|
when decompressing enable the STRICTGZIP define contained in AbDefine.inc }
|
|
|
|
{ validate against CRC }
|
|
if GzHelp.FItem.Crc32 <> GzHelp.TailCRC then
|
|
raise EAbGzipBadCRC.Create;
|
|
|
|
{ validate against file size }
|
|
if GzHelp.FItem.UncompressedSize <> GZHelp.TailSize then
|
|
raise EAbGzipBadFileSize.Create;
|
|
{$ENDIF}
|
|
finally
|
|
GzHelp.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TAbGzipArchive.FixName(const Value: string): string;
|
|
{ fix up fileaname for storage }
|
|
begin
|
|
if FState = gsTar then
|
|
Result := inherited FixName( Value )
|
|
else begin
|
|
{GZip files Always strip the file path}
|
|
StoreOptions := StoreOptions + [soStripDrive, soStripPath];
|
|
Result := '';
|
|
if Value <> '' then
|
|
Result := ExtractFileName(Value);
|
|
end;
|
|
end;
|
|
|
|
function TAbGzipArchive.GetIsGzippedTar: Boolean;
|
|
begin
|
|
Result := FIsGzippedTar;
|
|
end;
|
|
|
|
function TAbGzipArchive.GetItem(Index: Integer): TAbGzipItem;
|
|
begin
|
|
Result := nil;
|
|
if Index = 0 then
|
|
Result := TAbGzipItem(FItemList.Items[Index]);
|
|
end;
|
|
|
|
function TAbGzipArchive.GetSupportsEmptyFolders : Boolean;
|
|
begin
|
|
Result := IsGzippedTar and TarAutoHandle;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.LoadArchive;
|
|
var
|
|
GzHelp : TAbGzipStreamHelper;
|
|
Item : TAbGzipItem;
|
|
Abort : Boolean;
|
|
begin
|
|
SwapToGzip;
|
|
if FGzStream.Size > 0 then begin
|
|
GzHelp := TAbGzipStreamHelper.Create(FGzStream);
|
|
try
|
|
if GzHelp.FindFirstItem then begin
|
|
Item := TAbGzipItem.Create;
|
|
Item.LoadGzHeaderFromStream(FGzStream);
|
|
FGzStream.Seek(-SizeOf(TAbGzTailRec), soEnd);
|
|
GZHelp.ReadTail;
|
|
Item.CRC32 := GZHelp.TailCRC;
|
|
Item.UncompressedSize := GZHelp.TailSize;
|
|
|
|
Item.Action := aaNone;
|
|
FGZItem.Add(Item);
|
|
|
|
if IsGzippedTar and TarAutoHandle then begin
|
|
{ extract Tar and set stream up }
|
|
FTarStream.SwapFileDirectory := FTempDir;
|
|
GzHelp.SeekToItemData;
|
|
GzHelp.ExtractItemData(FTarStream);
|
|
SwapToTar;
|
|
inherited LoadArchive;
|
|
end;
|
|
end;
|
|
|
|
DoArchiveProgress(100, Abort);
|
|
FIsDirty := False;
|
|
finally
|
|
{ Clean Up }
|
|
GzHelp.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.PutItem(Index: Integer; const Value: TAbGzipItem);
|
|
begin
|
|
if Index = 0 then
|
|
FItemList.Items[Index] := Value;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.SaveArchive;
|
|
var
|
|
InGzHelp, OutGzHelp : TAbGzipStreamHelper;
|
|
Abort : Boolean;
|
|
i : Integer;
|
|
NewStream : TAbVirtualMemoryStream;
|
|
UncompressedStream : TStream;
|
|
SaveDir : string;
|
|
CurItem : TAbGzipItem;
|
|
begin
|
|
{prepare for the try..finally}
|
|
OutGzHelp := nil;
|
|
NewStream := nil;
|
|
|
|
try
|
|
InGzHelp := TAbGzipStreamHelper.Create(FGzStream);
|
|
|
|
try
|
|
{init new archive stream}
|
|
NewStream := TAbVirtualMemoryStream.Create;
|
|
OutGzHelp := TAbGzipStreamHelper.Create(NewStream);
|
|
|
|
{ create helper }
|
|
NewStream.SwapFileDirectory := FTempDir;
|
|
|
|
{ save the Tar data }
|
|
if IsGzippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
inherited SaveArchive;
|
|
if FGZItem.Count = 0 then begin
|
|
CurItem := TAbGzipItem.Create;
|
|
FGZItem.Add(CurItem);
|
|
end;
|
|
CurItem := FGZItem[0] as TAbGzipItem;
|
|
CurItem.Action := aaNone;
|
|
CurItem.LastModTimeAsDateTime := Now;
|
|
CurItem.SaveGzHeaderToStream(NewStream);
|
|
FTarStream.Position := 0;
|
|
OutGzHelp.WriteArchiveItem(FTarStream);
|
|
CurItem.CRC32 := OutGzHelp.CRC;
|
|
CurItem.UncompressedSize := OutGzHelp.FileSize;
|
|
OutGzHelp.WriteArchiveTail;
|
|
end
|
|
else begin
|
|
SwapToGzip;
|
|
|
|
{build new archive from existing archive}
|
|
for i := 0 to pred(Count) do begin
|
|
FCurrentItem := ItemList[i];
|
|
CurItem := TAbGzipItem(ItemList[i]);
|
|
InGzHelp.SeekToItemData;
|
|
|
|
case CurItem.Action of
|
|
aaNone, aaMove : begin
|
|
{just copy the file to new stream}
|
|
CurItem.SaveGzHeaderToStream(NewStream);
|
|
InGzHelp.SeekToItemData;
|
|
NewStream.CopyFrom(FGZStream, FGZStream.Size - FGZStream.Position);
|
|
end;
|
|
|
|
aaDelete: {doing nothing omits file from new stream} ;
|
|
|
|
aaAdd, aaFreshen, aaReplace, aaStreamAdd: begin
|
|
try
|
|
if (CurItem.Action = aaStreamAdd) then begin
|
|
{ adding from a stream }
|
|
CurItem.SaveGzHeaderToStream(NewStream);
|
|
CurItem.UncompressedSize := InStream.Size;
|
|
OutGzHelp.WriteArchiveItem(InStream);
|
|
OutGzHelp.WriteArchiveTail;
|
|
end
|
|
else begin
|
|
{ it's coming from a file }
|
|
GetDir(0, SaveDir);
|
|
try {SaveDir}
|
|
if (BaseDirectory <> '') then
|
|
ChDir(BaseDirectory);
|
|
CurItem.LastModTimeAsDateTime := AbGetFileTime(CurItem.DiskFileName);
|
|
UncompressedStream := TFileStream.Create(CurItem.DiskFileName,
|
|
fmOpenRead or fmShareDenyWrite );
|
|
finally {SaveDir}
|
|
ChDir( SaveDir );
|
|
end; {SaveDir}
|
|
|
|
try
|
|
CurItem.UncompressedSize := UncompressedStream.Size;
|
|
CurItem.SaveGzHeaderToStream(NewStream);
|
|
OutGzHelp.WriteArchiveItem(UncompressedStream);
|
|
OutGzHelp.WriteArchiveTail;
|
|
|
|
finally {UncompressedStream}
|
|
UncompressedStream.Free;
|
|
end; {UncompressedStream}
|
|
end;
|
|
except
|
|
ItemList[i].Action := aaDelete;
|
|
DoProcessItemFailure(ItemList[i], ptAdd, ecFileOpenError, 0);
|
|
end;
|
|
end;
|
|
end; {case}
|
|
end; { for }
|
|
end;
|
|
finally
|
|
InGzHelp.Free;
|
|
end;
|
|
|
|
{copy new stream to FStream}
|
|
SwapToGzip;
|
|
NewStream.Position := 0;
|
|
if (FStream is TMemoryStream) then
|
|
TMemoryStream(FStream).LoadFromStream(NewStream)
|
|
else if FOwnsStream then begin
|
|
{ need new stream to write }
|
|
FreeAndNil(FStream);
|
|
FGZStream := nil;
|
|
FStream := TFileStream.Create(FArchiveName, fmCreate or fmShareDenyWrite);
|
|
FGZStream := FStream;
|
|
FStream.CopyFrom(NewStream, NewStream.Size);
|
|
end
|
|
else begin
|
|
FStream.Size := 0;
|
|
FStream.Position := 0;
|
|
FStream.CopyFrom(NewStream, NewStream.Size);
|
|
end;
|
|
|
|
{update Items list}
|
|
for i := pred( Count ) downto 0 do begin
|
|
if ItemList[i].Action = aaDelete then
|
|
FItemList.Delete( i )
|
|
else if ItemList[i].Action <> aaFailed then
|
|
ItemList[i].Action := aaNone;
|
|
end;
|
|
|
|
if IsGzippedTar and TarAutoHandle then
|
|
SwapToTar;
|
|
|
|
DoArchiveSaveProgress( 100, Abort );
|
|
DoArchiveProgress( 100, Abort );
|
|
finally {NewStream}
|
|
OutGzHelp.Free;
|
|
NewStream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.SetTarAutoHandle(const Value: Boolean);
|
|
begin
|
|
if Value then
|
|
SwapToTar
|
|
else
|
|
SwapToGzip;
|
|
FTarAutoHandle := Value;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.TestItemAt(Index: Integer);
|
|
var
|
|
SavePos : LongInt;
|
|
GZType : TAbArchiveType;
|
|
BitBucket : TAbBitBucketStream;
|
|
GZHelp : TAbGzipStreamHelper;
|
|
begin
|
|
if IsGzippedTar and TarAutoHandle then begin
|
|
inherited TestItemAt(Index);
|
|
end
|
|
else begin
|
|
{ note Index ignored as there's only one item in a GZip }
|
|
SavePos := FGzStream.Position;
|
|
GZType := VerifyGZip(FGZStream);
|
|
if not (GZType in [atGZip, atGZippedTar]) then
|
|
raise EAbGzipInvalid.Create;
|
|
|
|
BitBucket := nil;
|
|
GZHelp := nil;
|
|
try
|
|
BitBucket := TAbBitBucketStream.Create(1024);
|
|
GZHelp := TAbGzipStreamHelper.Create(FGZStream);
|
|
|
|
GZHelp.ExtractItemData(BitBucket);
|
|
GZHelp.ReadTail;
|
|
|
|
{ validate against CRC }
|
|
if GzHelp.FItem.Crc32 <> GZHelp.TailCRC then
|
|
raise EAbGzipBadCRC.Create;
|
|
|
|
{ validate against file size }
|
|
if GzHelp.FItem.UncompressedSize <> GZHelp.TailSize then
|
|
raise EAbGzipBadFileSize.Create;
|
|
|
|
finally
|
|
GZHelp.Free;
|
|
BitBucket.Free;
|
|
end;
|
|
|
|
FGzStream.Position := SavePos;
|
|
end;
|
|
end;
|
|
|
|
procedure TAbGzipArchive.DoSpanningMediaRequest(Sender: TObject;
|
|
ImageNumber: Integer; var ImageName: string; var Abort: Boolean);
|
|
begin
|
|
Abort := False;
|
|
end;
|
|
|
|
end.
|