431 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
		
			14 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
 | 
						|
 * Joel Haynie
 | 
						|
 * Craig Peterson
 | 
						|
 *
 | 
						|
 * Portions created by the Initial Developer are Copyright (C) 1997-2002
 | 
						|
 * the Initial Developer. All Rights Reserved.
 | 
						|
 *
 | 
						|
 * Contributor(s):
 | 
						|
 *
 | 
						|
 * ***** END LICENSE BLOCK ***** *)
 | 
						|
 | 
						|
{*********************************************************}
 | 
						|
{* ABBREVIA: AbBzip2Typ.pas                              *}
 | 
						|
{*********************************************************}
 | 
						|
{* ABBREVIA: TAbBzip2Archive, TAbBzip2Item classes       *}
 | 
						|
{*********************************************************}
 | 
						|
{* Misc. constants, types, and routines for working      *}
 | 
						|
{* with Bzip2 files                                      *}
 | 
						|
{*********************************************************}
 | 
						|
 | 
						|
unit AbBzip2Typ;
 | 
						|
 | 
						|
{$I AbDefine.inc}
 | 
						|
 | 
						|
interface
 | 
						|
 | 
						|
uses
 | 
						|
  Classes,
 | 
						|
  AbArcTyp, AbTarTyp, AbUtils;
 | 
						|
 | 
						|
const
 | 
						|
  { Default Stream Header for Bzip2s is 'BZhX', where X is the block size setting 1-9 in ASCII }
 | 
						|
  { Each block has the following header: '1AY&SY', and are in units of 100kilobytes NOT 100kibiBytes }
 | 
						|
  AB_BZIP2_FILE_HEADER  = 'BZh';
 | 
						|
  AB_BZIP2_BLOCK_SIZE   = ['1','2','3','4','5','6','7','8','9'];
 | 
						|
  AB_BZIP2_BLOCK_HEADER = '1AY&SY'; { Note: $314159265359, BCD for Pi :) }
 | 
						|
  { Note that Blocks are bit aligned, as such the only time you will "for sure" see
 | 
						|
    the block header is on the start of stream/File }
 | 
						|
  AB_BZIP2_FILE_TAIL =#23#114#36#83#133#9#0; { $1772245385090, BCD for sqrt(Pi) :) }
 | 
						|
  { This is odd as the blocks are bit allgned so this is a string that is 13*4 bits = 52 bits }
 | 
						|
 | 
						|
type
 | 
						|
  PAbBzip2Header = ^TAbBzip2Header; { File Header }
 | 
						|
  TAbBzip2Header = packed record  { SizeOf(TAbBzip2Header) = 10 }
 | 
						|
    FileHeader  : array[0..2] of AnsiChar;{ 'BZh';    $42,5A,68 }
 | 
						|
    BlockSize   : AnsiChar;               { '1'..'9'; $31-$39 }
 | 
						|
    BlockHeader : array[0..5] of AnsiChar;{ '1AY&SY'; $31,41,59,26,53,59 }
 | 
						|
  end;
 | 
						|
 | 
						|
{ The Purpose for this Item is the placeholder for aaAdd and aaDelete Support. }
 | 
						|
{ For all intents and purposes we could just use a TAbArchiveItem }
 | 
						|
type
 | 
						|
  TAbBzip2Item = class(TabArchiveItem);
 | 
						|
 | 
						|
  TAbBzip2ArchiveState = (gsBzip2, gsTar);
 | 
						|
 | 
						|
  TAbBzip2Archive = class(TAbTarArchive)
 | 
						|
  private
 | 
						|
    FBzip2Stream  : TStream;        { stream for Bzip2 file}
 | 
						|
    FBzip2Item    : TAbArchiveList; { item in bzip2 (only one, but need polymorphism of class)}
 | 
						|
    FTarStream    : TStream;        { stream for possible contained Tar }
 | 
						|
    FTarList      : TAbArchiveList; { items in possible contained Tar }
 | 
						|
    FTarAutoHandle: Boolean;
 | 
						|
    FState        : TAbBzip2ArchiveState;
 | 
						|
    FIsBzippedTar : Boolean;
 | 
						|
 | 
						|
    procedure DecompressToStream(aStream: TStream);
 | 
						|
    procedure SetTarAutoHandle(const Value: Boolean);
 | 
						|
    procedure SwapToBzip2;
 | 
						|
    procedure SwapToTar;
 | 
						|
 | 
						|
  protected
 | 
						|
    { Inherited Abstract functions }
 | 
						|
    function CreateItem(const FileSpec : string): TAbArchiveItem; override;
 | 
						|
    procedure ExtractItemAt(Index : Integer; const NewName : string); override;
 | 
						|
    procedure ExtractItemToStreamAt(Index : Integer; aStream : TStream); override;
 | 
						|
    procedure LoadArchive; override;
 | 
						|
    procedure SaveArchive; override;
 | 
						|
    procedure TestItemAt(Index : Integer); override;
 | 
						|
    function GetSupportsEmptyFolders : Boolean; override;
 | 
						|
 | 
						|
  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;
 | 
						|
 | 
						|
    { Properties }
 | 
						|
    property TarAutoHandle : Boolean
 | 
						|
      read FTarAutoHandle write SetTarAutoHandle;
 | 
						|
 | 
						|
    property IsBzippedTar : Boolean
 | 
						|
      read FIsBzippedTar write FIsBzippedTar;
 | 
						|
  end;
 | 
						|
 | 
						|
function VerifyBzip2(Strm : TStream) : TAbArchiveType;
 | 
						|
 | 
						|
implementation
 | 
						|
 | 
						|
uses
 | 
						|
{$IFDEF MSWINDOWS}
 | 
						|
  Windows, // Fix inline warnings
 | 
						|
{$ENDIF}
 | 
						|
  StrUtils, SysUtils,
 | 
						|
  AbBzip2, AbExcept, AbVMStrm, AbBitBkt;
 | 
						|
 | 
						|
{ ****************** Helper functions Not from Classes Above ***************** }
 | 
						|
function VerifyHeader(const Header : TAbBzip2Header) : Boolean;
 | 
						|
begin
 | 
						|
  Result := (Header.FileHeader = AB_BZIP2_FILE_HEADER) and
 | 
						|
            (Header.BlockSize in AB_BZIP2_BLOCK_SIZE)  and
 | 
						|
            (Header.BlockHeader = AB_BZIP2_BLOCK_HEADER);
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
function VerifyBzip2(Strm : TStream) : TAbArchiveType;
 | 
						|
var
 | 
						|
  Hdr : TAbBzip2Header;
 | 
						|
  CurPos : int64;
 | 
						|
  DecompStream, TarStream: TStream;
 | 
						|
begin
 | 
						|
  Result := atUnknown;
 | 
						|
 | 
						|
  CurPos := Strm.Position;
 | 
						|
  Strm.Seek(0, soBeginning);
 | 
						|
 | 
						|
  try
 | 
						|
    if (Strm.Read(Hdr, SizeOf(Hdr)) = SizeOf(Hdr)) and VerifyHeader(Hdr) then begin
 | 
						|
      Result := atBzip2;
 | 
						|
      { Check for embedded TAR }
 | 
						|
      Strm.Seek(0, soBeginning);
 | 
						|
      DecompStream := TBZDecompressionStream.Create(Strm);
 | 
						|
      try
 | 
						|
        TarStream := TMemoryStream.Create;
 | 
						|
        try
 | 
						|
          TarStream.CopyFrom(DecompStream, 512 * 2);
 | 
						|
          TarStream.Seek(0, soBeginning);
 | 
						|
          if VerifyTar(TarStream) = atTar then
 | 
						|
            Result := atBzippedTar;
 | 
						|
        finally
 | 
						|
          TarStream.Free;
 | 
						|
        end;
 | 
						|
      finally
 | 
						|
        DecompStream.Free;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
  except
 | 
						|
    on EReadError do
 | 
						|
      Result := atUnknown;
 | 
						|
  end;
 | 
						|
  Strm.Position := CurPos; { Return to original position. }
 | 
						|
end;
 | 
						|
 | 
						|
 | 
						|
{ ****************************** TAbBzip2Archive ***************************** }
 | 
						|
constructor TAbBzip2Archive.CreateFromStream(aStream: TStream;
 | 
						|
  const aArchiveName: string);
 | 
						|
begin
 | 
						|
  inherited CreateFromStream(aStream, aArchiveName);
 | 
						|
  FState       := gsBzip2;
 | 
						|
  FBzip2Stream := FStream;
 | 
						|
  FBzip2Item   := FItemList;
 | 
						|
  FTarStream   := TAbVirtualMemoryStream.Create;
 | 
						|
  FTarList     := TAbArchiveList.Create(True);
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.SwapToTar;
 | 
						|
begin
 | 
						|
  FStream   := FTarStream;
 | 
						|
  FItemList := FTarList;
 | 
						|
  FState    := gsTar;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.SwapToBzip2;
 | 
						|
begin
 | 
						|
  FStream   := FBzip2Stream;
 | 
						|
  FItemList := FBzip2Item;
 | 
						|
  FState    := gsBzip2;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
function TAbBzip2Archive.CreateItem(const FileSpec: string): TAbArchiveItem;
 | 
						|
begin
 | 
						|
  if IsBzippedTar and TarAutoHandle then begin
 | 
						|
    SwapToTar;
 | 
						|
    Result := inherited CreateItem(FileSpec);
 | 
						|
  end
 | 
						|
  else begin
 | 
						|
    SwapToBzip2;
 | 
						|
    Result := TAbBzip2Item.Create;
 | 
						|
    try
 | 
						|
      Result.DiskFileName := ExpandFileName(FileSpec);
 | 
						|
      Result.FileName := FixName(FileSpec);
 | 
						|
    except
 | 
						|
      Result.Free;
 | 
						|
      raise;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
destructor TAbBzip2Archive.Destroy;
 | 
						|
begin
 | 
						|
  SwapToBzip2;
 | 
						|
  FTarList.Free;
 | 
						|
  FTarStream.Free;
 | 
						|
  inherited Destroy;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.ExtractItemAt(Index: Integer;
 | 
						|
  const NewName: string);
 | 
						|
var
 | 
						|
  OutStream : TFileStream;
 | 
						|
begin
 | 
						|
  if IsBzippedTar and TarAutoHandle then begin
 | 
						|
    SwapToTar;
 | 
						|
    inherited ExtractItemAt(Index, NewName);
 | 
						|
  end
 | 
						|
  else begin
 | 
						|
    SwapToBzip2;
 | 
						|
    OutStream := TFileStream.Create(NewName, fmCreate or fmShareDenyNone);
 | 
						|
    try
 | 
						|
      try
 | 
						|
        ExtractItemToStreamAt(Index, OutStream);
 | 
						|
      finally
 | 
						|
        OutStream.Free;
 | 
						|
      end;
 | 
						|
      { Bz2 doesn't store the last modified time or attributes, so don't set them }
 | 
						|
    except
 | 
						|
      on E : EAbUserAbort do begin
 | 
						|
        FStatus := asInvalid;
 | 
						|
        if FileExists(NewName) then
 | 
						|
          DeleteFile(NewName);
 | 
						|
        raise;
 | 
						|
      end else begin
 | 
						|
        if FileExists(NewName) then
 | 
						|
          DeleteFile(NewName);
 | 
						|
        raise;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.ExtractItemToStreamAt(Index: Integer;
 | 
						|
  aStream: TStream);
 | 
						|
begin
 | 
						|
  if IsBzippedTar and TarAutoHandle then begin
 | 
						|
    SwapToTar;
 | 
						|
    inherited ExtractItemToStreamAt(Index, aStream);
 | 
						|
  end
 | 
						|
  else begin
 | 
						|
    SwapToBzip2;
 | 
						|
    { Index ignored as there's only one item in a Bz2 }
 | 
						|
    DecompressToStream(aStream);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
function TAbBzip2Archive.GetSupportsEmptyFolders : Boolean;
 | 
						|
begin
 | 
						|
  Result := IsBzippedTar and TarAutoHandle;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.LoadArchive;
 | 
						|
var
 | 
						|
  Item: TAbBzip2Item;
 | 
						|
  Abort: Boolean;
 | 
						|
  ItemName: string;
 | 
						|
begin
 | 
						|
  if FBzip2Stream.Size = 0 then
 | 
						|
    Exit;
 | 
						|
 | 
						|
  if IsBzippedTar and TarAutoHandle then begin
 | 
						|
    { Decompress and send to tar LoadArchive }
 | 
						|
    DecompressToStream(FTarStream);
 | 
						|
    SwapToTar;
 | 
						|
    inherited LoadArchive;
 | 
						|
  end
 | 
						|
  else begin
 | 
						|
    SwapToBzip2;
 | 
						|
    Item := TAbBzip2Item.Create;
 | 
						|
    Item.Action := aaNone;
 | 
						|
    { Filename isn't stored, so constuct one based on the archive name }
 | 
						|
    ItemName := ExtractFileName(ArchiveName);
 | 
						|
    if ItemName = '' then
 | 
						|
      Item.FileName := 'unknown'
 | 
						|
    else if AnsiEndsText('.tbz', ItemName) or AnsiEndsText('.tbz2', ItemName) then
 | 
						|
      Item.FileName := ChangeFileExt(ItemName, '.tar')
 | 
						|
    else
 | 
						|
      Item.FileName := ChangeFileExt(ItemName, '');
 | 
						|
    Item.DiskFileName := Item.FileName;
 | 
						|
    FItemList.Add(Item);
 | 
						|
  end;
 | 
						|
  DoArchiveProgress(100, Abort);
 | 
						|
  FIsDirty := False;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.SaveArchive;
 | 
						|
var
 | 
						|
  CompStream: TStream;
 | 
						|
  i: Integer;
 | 
						|
  CurItem: TAbBzip2Item;
 | 
						|
  InputFileStream: TStream;
 | 
						|
begin
 | 
						|
  if IsBzippedTar and TarAutoHandle then
 | 
						|
  begin
 | 
						|
    SwapToTar;
 | 
						|
    inherited SaveArchive;
 | 
						|
    FTarStream.Position := 0;
 | 
						|
    FBzip2Stream.Size := 0;
 | 
						|
    CompStream := TBZCompressionStream.Create(bs9, FBzip2Stream);
 | 
						|
    try
 | 
						|
      CompStream.CopyFrom(FTarStream, 0);
 | 
						|
    finally
 | 
						|
      CompStream.Free;
 | 
						|
    end;
 | 
						|
  end
 | 
						|
  else begin
 | 
						|
    { Things we know: There is only one file per archive.}
 | 
						|
    { Actions we have to address in SaveArchive: }
 | 
						|
    { aaNone & aaMove do nothing, as the file does not change, only the meta data }
 | 
						|
    { aaDelete could make a zero size file unless there are two files in the list.}
 | 
						|
    { aaAdd, aaStreamAdd, aaFreshen, & aaReplace will be the only ones to take action. }
 | 
						|
    SwapToBzip2;
 | 
						|
    for i := 0 to pred(Count) do begin
 | 
						|
      FCurrentItem := ItemList[i];
 | 
						|
      CurItem      := TAbBzip2Item(ItemList[i]);
 | 
						|
      case CurItem.Action of
 | 
						|
        aaNone, aaMove: Break;{ Do nothing; bz2 doesn't store metadata }
 | 
						|
        aaDelete: ; {doing nothing omits file from new stream}
 | 
						|
        aaAdd, aaFreshen, aaReplace, aaStreamAdd: begin
 | 
						|
          FBzip2Stream.Size := 0;
 | 
						|
          CompStream := TBZCompressionStream.Create(bs9, FBzip2Stream);
 | 
						|
          try
 | 
						|
            if CurItem.Action = aaStreamAdd then
 | 
						|
              CompStream.CopyFrom(InStream, 0){ Copy/compress entire Instream to FBzip2Stream }
 | 
						|
            else begin
 | 
						|
              InputFileStream := TFileStream.Create(CurItem.DiskFileName, fmOpenRead or fmShareDenyWrite );
 | 
						|
              try
 | 
						|
                CompStream.CopyFrom(InputFileStream, 0);{ Copy/compress entire Instream to FBzip2Stream }
 | 
						|
              finally
 | 
						|
                InputFileStream.Free;
 | 
						|
              end;
 | 
						|
            end;
 | 
						|
          finally
 | 
						|
            CompStream.Free;
 | 
						|
          end;
 | 
						|
          Break;
 | 
						|
        end; { End aaAdd, aaFreshen, aaReplace, & aaStreamAdd }
 | 
						|
      end; { End of CurItem.Action Case }
 | 
						|
    end; { End Item for loop }
 | 
						|
  end; { End Tar Else }
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.SetTarAutoHandle(const Value: Boolean);
 | 
						|
begin
 | 
						|
  if Value then
 | 
						|
    SwapToTar
 | 
						|
  else
 | 
						|
    SwapToBzip2;
 | 
						|
  FTarAutoHandle := Value;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.DecompressToStream(aStream: TStream);
 | 
						|
const
 | 
						|
  BufSize = $F000;
 | 
						|
var
 | 
						|
  DecompStream: TBZDecompressionStream;
 | 
						|
  Buffer: PByte;
 | 
						|
  N: Integer;
 | 
						|
begin
 | 
						|
  DecompStream := TBZDecompressionStream.Create(FBzip2Stream);
 | 
						|
  try
 | 
						|
    GetMem(Buffer, BufSize);
 | 
						|
    try
 | 
						|
      N := DecompStream.Read(Buffer^, BufSize);
 | 
						|
      while N > 0 do begin
 | 
						|
        aStream.WriteBuffer(Buffer^, N);
 | 
						|
        N := DecompStream.Read(Buffer^, BufSize);
 | 
						|
      end;
 | 
						|
    finally
 | 
						|
      FreeMem(Buffer, BufSize);
 | 
						|
    end;
 | 
						|
  finally
 | 
						|
    DecompStream.Free;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.TestItemAt(Index: Integer);
 | 
						|
var
 | 
						|
  Bzip2Type: TAbArchiveType;
 | 
						|
  BitBucket: TAbBitBucketStream;
 | 
						|
begin
 | 
						|
  if IsBzippedTar and TarAutoHandle then begin
 | 
						|
    SwapToTar;
 | 
						|
    inherited TestItemAt(Index);
 | 
						|
  end
 | 
						|
  else begin
 | 
						|
    { note Index ignored as there's only one item in a GZip }
 | 
						|
    Bzip2Type := VerifyBzip2(FBzip2Stream);
 | 
						|
    if not (Bzip2Type in [atBzip2, atBzippedTar]) then
 | 
						|
      raise EAbGzipInvalid.Create;// TODO: Add bzip2-specific exceptions }
 | 
						|
    BitBucket := TAbBitBucketStream.Create(1024);
 | 
						|
    try
 | 
						|
      DecompressToStream(BitBucket);
 | 
						|
    finally
 | 
						|
      BitBucket.Free;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
{ -------------------------------------------------------------------------- }
 | 
						|
procedure TAbBzip2Archive.DoSpanningMediaRequest(Sender: TObject;
 | 
						|
  ImageNumber: Integer; var ImageName: string; var Abort: Boolean);
 | 
						|
begin
 | 
						|
  Abort := False;
 | 
						|
end;
 | 
						|
 | 
						|
end.
 |