1645 lines
56 KiB
ObjectPascal

////////////////////////////////////////////////////////////////////////////////
//
// ****************************************************************************
// * Project : FWZip
// * Unit Name : FWZipReader
// * Purpose : Íàáîð êëàññîâ äëÿ ðàñïàêîâêè ZIP àðõèâà
// * Author : Àëåêñàíäð (Rouse_) Áàãåëü
// * Copyright : © Fangorn Wizards Lab 1998 - 2023.
// * Version : 2.0.1
// * Home Page : http://rouse.drkb.ru
// * Home Blog : http://alexander-bagel.blogspot.ru
// ****************************************************************************
// * Stable Release : http://rouse.drkb.ru/components.php#fwzip
// * Latest Source : https://github.com/AlexanderBagel/FWZip
// ****************************************************************************
//
// Èñïîëüçóåìûå èñòî÷íèêè:
// ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip
// https://zlib.net/zlib-1.2.13.tar.gz
// http://www.base2ti.com/
//
unit FWZipReader;
{$IFDEF FPC}
{$MODE Delphi}
{$H+}
{$ENDIF}
interface
{$I fwzip.inc}
uses
SysUtils,
Classes,
Contnrs,
Masks,
FWZipConsts,
FWZipCrc32,
FWZipCrypt,
FWZipStream,
FWZipZLib,
FWZipUtils;
type
TFWZipReader = class;
TExtractResult = (erError, erDone, erNeedPassword, erWrongCRC32, erSkiped);
TPresentStream = (ssZIP64, ssNTFS);
TPresentStreams = set of TPresentStream;
TFWZipReaderItem = class
private
FOwner: TFWZipReader;
FLocalFileHeader: TLocalFileHeader;
FFileHeader: TCentralDirectoryFileHeaderEx;
FIsFolder: Boolean;
FOnProgress: TZipExtractItemEvent;
FTotalExtracted, FExtractStreamStartSize: Int64;
FExtractStream: TStream;
FItemIndex, FTag: Integer;
FDuplicate: TZipDuplicateEvent;
FPresentStreams: TPresentStreams;
function GetString(const Index: Integer): string;
protected
procedure DoProgress(Sender: TObject; ProgressState: TProgressState);
procedure DecompressorOnProcess(Sender: TObject);
procedure LoadExData;
procedure LoadStringValue(var Value: string; nSize: Cardinal;
CheckEncoding: Boolean);
procedure LoadLocalFileHeader;
{%H-}constructor InitFromStream(Owner: TFWZipReader; Index: Integer);
protected
property LocalFileHeader: TLocalFileHeader read FLocalFileHeader;
property CentralDirFileHeader: TCentralDirectoryFileHeader
read FFileHeader.Header;
property CentralDirFileHeaderEx: TCentralDirectoryFileHeaderEx read FFileHeader;
property RelativeOffsetOfLocalHeader: Int64 read
FFileHeader.RelativeOffsetOfLocalHeader;
property DiskNumberStart: Integer read FFileHeader.DiskNumberStart;
public
function CreateDecompressionStream: TStream;
function Extract(const Path, Password: string): TExtractResult; overload;
function Extract(const Path, NewFileName, Password: string): TExtractResult; overload;
function ExtractToStream(Value: TStream; const Password: string;
CheckCRC32: Boolean = True): TExtractResult;
property Attributes: TFileAttributeData read FFileHeader.Attributes;
property Comment: string index 0 read GetString;
property ItemIndex: Integer read FItemIndex;
property IsFolder: Boolean read FIsFolder;
property FileName: string index 1 read GetString;
property VersionMadeBy: Word read FFileHeader.Header.VersionMadeBy;
property VersionNeededToExtract: Word read
FFileHeader.Header.VersionNeededToExtract;
property CompressionMethod: Word read FFileHeader.Header.CompressionMethod;
property LastModFileTime: Word read FFileHeader.Header.LastModFileTimeTime;
property LastModFileDate: Word read FFileHeader.Header.LastModFileTimeDate;
property Crc32: Cardinal read FFileHeader.Header.Crc32;
property CompressedSize: Int64 read FFileHeader.CompressedSize;
property PresentStreams: TPresentStreams read FPresentStreams;
property Tag: Integer read FTag write FTag;
property UncompressedSize: Int64 read FFileHeader.UncompressedSize;
property OnProgress: TZipExtractItemEvent
read FOnProgress write FOnProgress;
property OnDuplicate: TZipDuplicateEvent read FDuplicate write FDuplicate;
end;
TFWZipReader = class
private
FZIPStream, FFileStream: TStream;
FLocalFiles: TObjectList;
FZip64EOFCentralDirectoryRecord: TZip64EOFCentralDirectoryRecord;
FZip64EOFCentralDirectoryLocator: TZip64EOFCentralDirectoryLocator;
FEndOfCentralDir: TEndOfCentralDir;
FEndOfCentralDirComment: AnsiString;
FOnProgress: TZipProgressEvent;
FOnNeedPwd: TZipNeedPasswordEvent;
FTotalSizeCount, FTotalProcessedCount: Int64;
FPasswordList: TStringList;
FOnLoadExData: TZipLoadExDataEvent;
FException: TZipExtractExceptionEvent;
FDuplicate: TZipDuplicateEvent;
FStartZipDataOffset, FEndZipDataOffset: Int64;
FDefaultDuplicateAction: TDuplicateAction;
function GetItem(Index: Integer): TFWZipReaderItem;
procedure SetDefaultDuplicateAction(const Value: TDuplicateAction);
protected
property ZIPStream: TStream read FZIPStream;
// Rouse_ 02.10.2012
// Äîáàâëåíû ïîëÿ äëÿ óêàçàíèÿ êàñòîìíîé ïîçèöèè àðõèâà â ñòðèìå ñ äàííûìè
property StartZipDataOffset: Int64 read FStartZipDataOffset;
property EndZipDataOffset: Int64 read FEndZipDataOffset;
protected
function IsMultiPartZip: Boolean;
function Zip64Present: Boolean;
function SizeOfCentralDirectory: Int64;
function TotalEntryesCount: Integer;
procedure LoadStringValue(var Value: AnsiString; nSize: Cardinal);
procedure LoadEndOfCentralDirectory;
procedure LoadZIP64Locator;
procedure LoadZip64EOFCentralDirectoryRecord;
procedure LoadCentralDirectoryFileHeader;
procedure ProcessExtractOrCheckAllData(const ExtractMask: string;
Path: string; CheckMode: Boolean);
procedure SetStreamPosition(DiskNumber: Integer; Offset: Int64);
protected
procedure DoProgress(Sender: TObject;
const FileName: string; Extracted, TotalSize: Int64;
ProgressState: TProgressState);
protected
property Zip64EOFCentralDirectoryRecord: TZip64EOFCentralDirectoryRecord
read FZip64EOFCentralDirectoryRecord;
property Zip64EOFCentralDirectoryLocator: TZip64EOFCentralDirectoryLocator
read FZip64EOFCentralDirectoryLocator;
property EndOfCentralDir: TEndOfCentralDir read FEndOfCentralDir;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
/// <summary>
/// Ôóíêöèÿ èùåò çàïèñü îá ýëåìåíòå ïî èìåíè
/// </summary>
function Find(const Value: string; out AItem: TFWZipReaderItem;
IgnoreCase: Boolean = True): Boolean; overload;
function GetElementIndex(const FileName: string): Integer;
procedure LoadFromFile(const Value: string; SFXOffset: Integer = -1;
ZipEndOffset: Integer = -1);
procedure LoadFromStream(Value: TStream; SFXOffset: Integer = -1;
ZipEndOffset: Integer = -1);
procedure ExtractAll(const Path: string); overload;
procedure ExtractAll(const ExtractMask: string; Path: string); overload;
procedure Check(const ExtractMask: string = '');
function Count: Integer;
property DefaultDuplicateAction: TDuplicateAction
read FDefaultDuplicateAction write SetDefaultDuplicateAction;
property Item[Index: Integer]: TFWZipReaderItem read GetItem; default;
property Comment: AnsiString read FEndOfCentralDirComment;
property PasswordList: TStringList read FPasswordList;
property OnProgress: TZipProgressEvent read FOnProgress write FOnProgress;
property OnPassword: TZipNeedPasswordEvent
read FOnNeedPwd write FOnNeedPwd;
property OnLoadExData: TZipLoadExDataEvent
read FOnLoadExData write FOnLoadExData;
property OnException: TZipExtractExceptionEvent
read FException write FException;
property OnDuplicate: TZipDuplicateEvent read FDuplicate write FDuplicate;
end;
EWrongPasswordException = class(Exception);
EZipReaderItem = class(Exception);
EZipReader = class(Exception);
EZipReaderRead = class(Exception);
implementation
{ TFWZipReaderItem }
//
// Ñòðèì äëÿ ðàáîòû ñ äàííûìè èçâíå
// =============================================================================
function TFWZipReaderItem.CreateDecompressionStream: TStream;
var
RealCompressedSize: Int64;
ZipItemStream: TFWZipItemStream;
begin
Result := nil;
if IsFolder then Exit;
if FFileHeader.DataOffset = 0 then
LoadLocalFileHeader;
// Rouse_ 16.10.2017
// Ïîêà çàøèôðîâàííûå íå ïîääåðæèâàåì, ïîòîì äîäåëàþ
if FFileHeader.Header.GeneralPurposeBitFlag and PBF_CRYPTED <> 0 then
raise EZipReaderItem.Create('CreateDecompressionStream íå ïîääåðæèâàåòñÿ äëÿ çàøèôðîâàííûõ äîêóìåíòîâ');
FOwner.FZIPStream.Position := FFileHeader.DataOffset;
RealCompressedSize := FFileHeader.CompressedSize;
case FFileHeader.Header.CompressionMethod of
Z_NO_COMPRESSION:
Result := TFWZipItemItemUnpackedStream.Create(FOwner.FZIPStream,
FFileHeader.DataOffset, UncompressedSize);
Z_DEFLATED:
begin
ZipItemStream := TFWZipItemStream.Create(FOwner.FZIPStream,
nil, nil,
FFileHeader.Header.GeneralPurposeBitFlag and 6,
RealCompressedSize
{$IFNDEF USE_AUTOGENERATED_ZLIB_HEADER}
+ 4
{$ENDIF}
);
Result := TZDecompressionStream.Create(
ZipItemStream, defaultWindowBits, True);
end;
end;
end;
//
// Îáðàáîò÷èê OnProcess ó ðàñïàêîâùèêà
// =============================================================================
procedure TFWZipReaderItem.DecompressorOnProcess(Sender: TObject);
begin
DoProgress(Sender, psInProgress);
end;
//
// Ïðîöåäóðà âûçûâàåò âíåøíåå ñîáûòèå OnProcess
// =============================================================================
procedure TFWZipReaderItem.DoProgress(Sender: TObject;
ProgressState: TProgressState);
begin
if Assigned(FOnProgress) then
if Sender = nil then
FOnProgress(Self, FileName, FTotalExtracted,
UncompressedSize, ProgressState)
else
begin
FTotalExtracted := FExtractStream.Size - FExtractStreamStartSize;
FOnProgress(Self, FileName, FTotalExtracted,
UncompressedSize, ProgressState);
end;
end;
//
// Ôóíêöèÿ ðàñïàêîâûâàåò òåêóùèé ýëåìåíò àðõâà â óêàçàííóþ ïàïêó
// =============================================================================
function TFWZipReaderItem.Extract(const Path, Password: string): TExtractResult;
begin
Result := Extract(Path, '', Password);
end;
//
// Ôóíêöèÿ ðàñïàêîâûâàåò òåêóùèé ýëåìåíò àðõâà â óêàçàííûé ôàéë
// =============================================================================
function TFWZipReaderItem.Extract(
const Path, NewFileName, Password: string): TExtractResult;
var
UnpackedFile: TFileStream;
FullPath: string;
FileDate: Integer;
DuplicateAction: TDuplicateAction;
ResultFileName: string;
begin
Result := erDone;
// Ïðàâêà ïóñòîãî è îòíîñèòåëüíîãî ïóòè
FullPath := PathCanonicalize(Path);
if Path = '' then
FullPath := GetCurrentDir;
// Rouse_ 19.05.2021
// Äàåì âîçìîæíîñòü ïîìåíÿòü èìÿ ðàñïàêîâûâàåìîãî ôàéëà íà ëåòó
// Îòäåëüíîå ñïàñèáî Àðòóðó Àëååâó çà îáíàðóæåíèå íåâåðíîé ðàáîòû ñ ïàðàìåòðîì
if NewFileName = '' then
ResultFileName := FFileHeader.FileName
else
ResultFileName := NewFileName;
FullPath := IncludeTrailingPathDelimiter(FullPath) + ResultFileName;
{$IFDEF MSWINDOWS}
FullPath := StringReplace(FullPath, ZIP_SLASH, '\', [rfReplaceAll]);
{$ENDIF}
// BAD CODE
// // Rouse_ 23.03.2015
// // Äàåì âîçìîæíîñòü ïîìåíÿòü èìÿ ðàñïàêîâûâàåìîãî ôàéëà íà ëåòó
// if NewFileName <> '' then
// FullPath := ExtractFilePath(FullPath) + NewFileName;
// Rouse_ 20.12.2019
// Äàåì âîçìîæíîñòü ðàáîòû ñ äëèííûìè ïóòÿìè
// if Length(FullPath) > MAX_PATH then
// raise EZipReaderItem.CreateFmt(
// 'Ýëåìåíò àðõèâà ¹%d "%s" íå ìîæåò áûòü ðàñïàêîâàí.' + sLineBreak +
// 'Îáùàÿ äëèíà ïóòè è èìåíè ôàéëà íå äîëæíà ïðåâûøàòü 260 ñèìâîëîâ',
// [ItemIndex, FFileHeader.FileName]);
if IsFolder then
begin
ForceDirectoriesEx(FullPath);
Exit;
end;
ForceDirectoriesEx(ExtractFilePath(FullPath));
try
// ïðîâåðêà íà ñóùåñòâîâàíèå ôàéëà
if FileExists(FullPath) then
begin
// åñëè ôàéë óæå ñóùåñòâóåò, óçíàåì - êàê æèòü äàëüøå ñ ýòèì ;)
DuplicateAction := FOwner.DefaultDuplicateAction;
if Assigned(FDuplicate) then
FDuplicate(Self, FullPath, DuplicateAction);
case DuplicateAction of
// ïðîïóñòèòü ôàéë
daSkip:
begin
Result := erSkiped;
Exit;
end;
// ïåðåçàïèñàòü
daOverwrite:
SetNormalFileAttributes(FullPath);
// ðàñïàêîâàòü ñ äðóãèì èìåíåì
daUseNewFilePath:
// åñëè ïðîãðàììèñò óêàçàë íîâûé ïóñòü ê ôàéëó,
// òî î ñóùåñòâîâàíèè äèðåêòîðèè îí äîëæåí ïîçàáîòèòüñÿ ñàì
if not DirectoryExists(ExtractFilePath(FullPath)) then
begin
Result := erSkiped;
Exit;
end;
// ïðåðâàòü ðàñïàêîâêó
daAbort:
Abort;
end;
end;
UnpackedFile := TFileStream.Create(FullPath, fmCreate);
try
Result := ExtractToStream(UnpackedFile, Password);
finally
UnpackedFile.Free;
end;
if Result <> erDone then
begin
DeleteFile(FullPath);
Exit;
end;
if IsAttributesPresent(FFileHeader.Attributes) then
SetFileAttributes(FullPath, FFileHeader.Attributes)
else
begin
FileDate :=
FFileHeader.Header.LastModFileTimeTime +
FFileHeader.Header.LastModFileTimeDate shl 16;
FileSetDate(FullPath, FileDate);
end;
except
DeleteFile(FullPath);
raise;
end;
end;
//
// Ôóíêöèÿ ðàñïàêîâûâàåò òåêóùèé ýëåìåíò àðõâà â ñòðèì
// =============================================================================
function TFWZipReaderItem.ExtractToStream(Value: TStream;
const Password: string; CheckCRC32: Boolean): TExtractResult;
function CopyWithProgress(Src, Dst: TStream; Count: Int64;
Decryptor: TFWZipDecryptor): Cardinal;
var
Buff: Pointer;
Size: Integer;
begin
Result := $FFFFFFFF;
try
GetMem(Buff, MAXWORD);
try
Size := MAXWORD;
DoProgress(nil, psInitialization);
while Size = MAXWORD do
begin
if Count - FTotalExtracted < MAXWORD then
Size := Count - FTotalExtracted;
if Src.Read(Buff^, Size) <> Size then
raise EZipReaderRead.CreateFmt(
'Îøèáêà ÷òåíèÿ äàííûõ ýëåìåíòà ¹%d "%s".', [ItemIndex, FileName]);
if Decryptor <> nil then
Decryptor.DecryptBuffer(Buff, Size);
Result := CRC32Calc(Result, Buff, Size);
Dst.WriteBuffer(Buff^, Size);
Inc(FTotalExtracted, Size);
DoProgress(nil, psInProgress);
end;
DoProgress(nil, psFinalization);
finally
FreeMem(Buff);
end;
Result := Result xor $FFFFFFFF;
except
DoProgress(nil, psException);
raise;
end;
end;
procedure RaiseStrongCrypt;
begin
raise EZipReaderItem.CreateFmt(
'Îøèáêà èçâëå÷åíèÿ äàííûõ ýëåìåíòà ¹%d "%s".' + sLineBreak +
'Íå ïîääåðæèâàåìûé ðåæèì øèôðîâàíèÿ',
[ItemIndex, FileName]);
end;
const
CompressionMetods: array [0..12] of string = (
'Store',
'Shrunk',
'Reduced1',
'Reduced2',
'Reduced3',
'Reduced4',
'Imploded',
'Tokenizing compression algorithm',
'Deflate',
'Deflate64',
'PKWARE Data Compression Library Imploding',
'PKWARE',
'BZIP2'
);
var
Decompressor: TZDecompressionStream;
ZipItemStream: TFWZipItemStream;
Decryptor: TFWZipDecryptor;
RealCompressedSize: Int64;
CurrItemCRC32: Cardinal;
CRC32Stream: TFWZipCRC32Stream;
begin
Result := erError;
{$IFNDEF FPC}
{$IF COMPILERVERSION < 32.0 }
CurrItemCRC32 := 0; // Tokyo çäåñü ãåíåðèðóåò âîðíèíã, â îòëè÷èå îò ñòàðûõ êîìïèëåðîâ
{$IFEND}
{$ENDIF}
FTotalExtracted := 0;
Decryptor := nil;
try
if IsFolder then Exit;
// Äàííûå äëÿ ðàñïàêîâêè íàõîäÿòñÿ ñðàçó çà LocalFileHeader.
// Äëÿ ïîëó÷åíèÿ îôôñåòà íà íà÷àëî äàííûõ íåîáõîäèìî ðàñïàðñèòü
// äàííóþ ñòðóêòóðó âêëþ÷àÿ áëîêè ñ äîïîëíèòåëüíîé èíôîðìàöèåé.
if FFileHeader.DataOffset = 0 then
LoadLocalFileHeader;
FOwner.ZIPStream.Position := FFileHeader.DataOffset;
RealCompressedSize := FFileHeader.CompressedSize;
// Ïðîâåðêà íåïîääåðæèâàåìîé AES êðèïòîãðàôèè ÷åðåç
// óêàçàííûé ìåòîä êîìïðåññèè
if FFileHeader.Header.CompressionMethod = Z_AES_COMPRESSION then
RaiseStrongCrypt;
// Åñëè ôàéë çàøèôðîâàí, íåîáõîäèìî èíèöèàëèçèðîâàòü êëþ÷ äëÿ ðàñïàêîâêè
if FFileHeader.Header.GeneralPurposeBitFlag and PBF_CRYPTED <> 0 then
begin
if FFileHeader.Header.GeneralPurposeBitFlag and
PBF_STRONG_CRYPT <> 0 then
RaiseStrongCrypt;
if Password = '' then
begin
// ïàðîëü íå ìîæåò áûòü ïóñòûì
Result := erNeedPassword;
Exit;
end;
Decryptor := TFWZipDecryptor.Create(AnsiString(Password));
if not Decryptor.LoadEncryptionHeader(FOwner.FZIPStream,
FFileHeader.Header.GeneralPurposeBitFlag and PBF_DESCRIPTOR <> 0,
FFileHeader.Header.Crc32,
FFileHeader.Header.LastModFileTimeTime +
FFileHeader.Header.LastModFileTimeDate shl 16) then
begin
// îøèêà èíèöèàëèçàöèè êëþ÷à
Result := erNeedPassword;
Exit;
end
else
// åñëè êëþ÷ èíèöèàëèçèðîâàí óñïåøíî - âû÷èòàåì èç ñæàòîãî ðàçìåðà
// ðàçìåð çàãîëîâêà èíèöèàëèçàöèè êëþ÷à
Dec(RealCompressedSize, EncryptedHeaderSize);
end;
case FFileHeader.Header.CompressionMethod of
Z_NO_COMPRESSION:
begin
CurrItemCRC32 :=
CopyWithProgress(FOwner.FZIPStream, Value,
UncompressedSize, Decryptor);
// Rouse_ 11.03.2011
// À âûñòàâèòü ðåçóëüòàò òî è çàáûëè.
// Cïàñèáî Ðîìêèíó çà îáíàðóæåíèå êîñÿêà
Result := erDone;
end;
Z_DEFLATED:
begin
// TFWZipItemStream âûñòóïàåò êàê ïîñðåäíèê ìåæäó FOwner.FZIPStream
// è TDecompressionStream. Åãî çàäà÷à äîáàâèòü â ïåðåäàâàåìûé
// áóôôåð äàííûõ îòñóòñòâóþùèé ZLib çàãîëîâîê è ðàñøèôðîâàòü
// äàííûå ïðè íåîáõîäèìîñòè
ZipItemStream := TFWZipItemStream.Create(FOwner.FZIPStream,
nil, Decryptor,
FFileHeader.Header.GeneralPurposeBitFlag and 6,
RealCompressedSize
{$IFNDEF USE_AUTOGENERATED_ZLIB_HEADER}
+ 4 // áóôôåð, îí âñå ðàâíî íå èñïîëüçóåòñÿ,
// íî íóæåí äëÿ çàâåðøåíèÿ ZInflate ïðè èñïîëüçîâàíèè windowBits
// îñîáåííî äëÿ àðõèâîâ çàïàêîâàííûõ 7Zip
{$ENDIF}
);
try
Decompressor := TZDecompressionStream.Create(
ZipItemStream, defaultWindowBits);
try
Decompressor.OnProgress := DecompressorOnProcess;
FExtractStreamStartSize := Value.Size;
FExtractStream := Value;
// TFWZipCRC32Stream âûñòóïàåò êàê ïîñðåäíèê ìåæäó
// TDecompressionStream è ðåçóëüòèðóþùèì ñòðèìîì,
// â êîòîðûé ïðîèñõîäèò ðàñïàêîâêà äàííûõ.
// Åãî çàäà÷à îòñëåäèòü âñå ðàñïàêîâàííûå áëîêè äàííûõ
// è ðàññ÷èòàòü èõ êîíòðîëüíóþ ñóììó
DoProgress(Decompressor, psInitialization);
CRC32Stream := TFWZipCRC32Stream.Create(Value);
try
try
// Rouse_ 16.06.2022
// Ôèêñ îøèáêè ñ ïàäåíèåì âíóòðè VCL ïî ðàçìåðó "ìèíóñ 1"
if UncompressedSize > 0 then
CRC32Stream.CopyFrom(Decompressor, UncompressedSize);
except
// EOutOfMemory íàâåðõ êàê åñòü
on E: EOutOfMemory do
raise;
on E: EReadError do
raise EZipReaderRead.CreateFmt(
'Îøèáêà ÷òåíèÿ äàííûõ ýëåìåíòà ¹%d "%s".', [ItemIndex, FileName]);
// Rouse_ 04.04.2010
// Ðàíåå ýòî èñêëþ÷åíèÿå áûëî EDecompressionError
// Ïîýòîìó ïðèâÿæåìñÿ ê áàçîâîìó èñêëþ÷åíèþ EZLibError
// on E: EZDecompressionError do
on E: EZLibError do
begin
if FFileHeader.Header.GeneralPurposeBitFlag and
PBF_CRYPTED <> 0 then
begin
// Îøèáêà ìîæåò ïîäíÿòüñÿ èç-çà òîãî ÷òî èíèöèàëèçàöèÿ
// êðèïòîçàãîëîâêà ïðîøëà óñïåøíî, íî ïàðîëü áûë óêàçàí íå âåðíûé
// Òàêîå ìîæåò ïðîèçîéòè, ò.ê. êîëè÷åñòâî êîëëèçèé
// ïðè ïðîâåðêå çàãîëîâêà î÷åíü âåëèêî
Result := erNeedPassword;
Exit;
end
else
DoProgress(Decompressor, psException);
raise EZipReaderRead.CreateFmt(string(
'Îøèáêà ðàñïàêîâêè äàííûõ ýëåìåíòà ¹%d "%s".' + sLineBreak) +
ExceptionMessage(E), [ItemIndex, FileName]);
end;
// Rouse_ 01.11.2013
// Äëÿ îñòàëüíûõ èñêëþ÷åíèé òîæå íóæíî ãîâîðèòü ñ êàêèì ýëåìåíòîì áåäà ïðèêëþ÷èëàñü.
on E: Exception do
raise EZipReaderRead.CreateFmt(string(
'Îøèáêà ðàñïàêîâêè äàííûõ ýëåìåíòà ¹%d "%s".' + sLineBreak) +
ExceptionMessage(E), [ItemIndex, FileName]);
end;
CurrItemCRC32 := CRC32Stream.CRC32;
finally
CRC32Stream.Free;
end;
DoProgress(Decompressor, psFinalization);
Result := erDone;
finally
Decompressor.Free;
end;
finally
ZipItemStream.Free;
end;
end;
1..7, 9..12:
raise EZipReaderItem.CreateFmt(
'Îøèáêà èçâëå÷åíèÿ äàííûõ ýëåìåíòà ¹%d "%s".' + sLineBreak +
'Íå ïîääåðæèâàåìûé àëãîðèòì äåêîìïðåññèè "%s"',
[ItemIndex, FileName, CompressionMetods[CompressionMethod]]);
else
raise EZipReaderItem.CreateFmt(
'Îøèáêà èçâëå÷åíèÿ äàííûõ ýëåìåíòà ¹%d "%s".' + sLineBreak +
'Íå ïîääåðæèâàåìûé àëãîðèòì äåêîìïðåññèè (%d)',
[ItemIndex, FileName, FFileHeader.Header.CompressionMethod]);
end;
if CurrItemCRC32 <> Crc32 then
if CheckCRC32 then
raise EZipReaderItem.CreateFmt(
'Îøèáêà èçâëå÷åíèÿ äàííûõ ýëåìåíòà ¹%d "%s".' + sLineBreak +
'Íåâåðíàÿ êîíòðîëüíàÿ ñóììà.',
[ItemIndex, FileName])
else
Result := erWrongCRC32;
finally
Decryptor.Free;
end;
end;
//
// =============================================================================
function TFWZipReaderItem.GetString(const Index: Integer): string;
begin
case Index of
0: Result := FFileHeader.FileComment;
1: Result := FFileHeader.FileName;
end;
end;
//
// Êîíñòðóêòîð ýëåìåíòà àðõèâà.
// Èíèöèàëèçàöèÿ êëàññà ïðîèñõîäèò íà îñíîâå äàííûõ èç àðõèâà
// =============================================================================
constructor TFWZipReaderItem.InitFromStream(Owner: TFWZipReader; Index: Integer);
var
Len: Integer;
begin
inherited Create;
FOwner := Owner;
FItemIndex := Index;
ZeroMemory(@FFileHeader, SizeOf(TCentralDirectoryFileHeaderEx));
if Owner.ZIPStream.Read(FFileHeader.Header,
SizeOf(TCentralDirectoryFileHeader)) <> SizeOf(TCentralDirectoryFileHeader) then
raise EZipReaderRead.CreateFmt(
'Îòñóòñòâóþò äàííûå TCentralDirectoryFileHeader ýëåìåíòà ¹%d', [ItemIndex]);
if FFileHeader.Header.CentralFileHeaderSignature <>
CENTRAL_FILE_HEADER_SIGNATURE then
raise EZipReaderItem.CreateFmt(
'Îøèáêà ÷òåíèÿ ñòðóêòóðû TCentralDirectoryFileHeader ýëåìåíòà ¹%d', [ItemIndex]);
LoadStringValue(FFileHeader.FileName, FFileHeader.Header.FilenameLength, True);
FIsFolder := FFileHeader.Header.ExternalFileAttributes and faDirectory <> 0;
// Rouse_ 31.08.2015
// Åñëè èñïîëüçóåì UTF8 òî FilenameLength ýòî ðàçìåð â áàéòàõ à íå â ñèìâîëàõ
// ïîýòîìó âìåñòî ýòîãî:
//if FFileHeader.Header.FilenameLength > 0 then
// FIsFolder := FIsFolder or
// (FFileHeader.FileName[FFileHeader.Header.FilenameLength] = ZIP_SLASH);
// ïèøåì âîò òàê:
Len := Length(FFileHeader.FileName);
if Len > 0 then
FIsFolder := FIsFolder or
(FFileHeader.FileName[Len] = ZIP_SLASH);
// Ñëåäóþùèå 4 ïàðàìåòðà ìîãóò áûòü âûñòàâëåíû â -1 èç-çà ïåðåïîëíåíèÿ
// è èõ ðåàëüíûå çíà÷åíèÿ áóäóò ñîäåðæàòüñÿ â áëîêå ðàñøèðåííûõ äàííûõ.
// Çàïîìèíàåì èõ òåêóùèå çíà÷åíèÿ.
//  ñëó÷àå åñëè êàêîé-ëèáî èç ïàðàìåòðîâ âûñòàâëåí â -1,
// åãî çíà÷åíèå ïîìåíÿåòñÿ ïðè âûçîâå ïðîöåäóðû LoadExData.
FFileHeader.UncompressedSize := FFileHeader.Header.UncompressedSize;
FFileHeader.CompressedSize := FFileHeader.Header.CompressedSize;
FFileHeader.RelativeOffsetOfLocalHeader :=
FFileHeader.Header.RelativeOffsetOfLocalHeader;
FFileHeader.DiskNumberStart := FFileHeader.Header.DiskNumberStart;
LoadExData;
LoadStringValue(FFileHeader.FileComment,
FFileHeader.Header.FileCommentLength, False);
// ÷àñòü èíôîðìàöèè äóáëèðóåòñÿ â ðàñøèðåííîì çàãîëîâêå
// íåîáõîäèìî åå çàïîëíèòü
FFileHeader.Attributes.dwFileAttributes :=
FFileHeader.Header.ExternalFileAttributes;
FFileHeader.Attributes.nFileSizeHigh :=
Cardinal(FFileHeader.UncompressedSize shr 32);
FFileHeader.Attributes.nFileSizeLow :=
FFileHeader.UncompressedSize and MAXDWORD;
end;
//
// Ïðîöåäóðà çà÷èòûâàåò äîïîëíèòåëüíûå äàííûå î ýëåìåíòå
// =============================================================================
procedure TFWZipReaderItem.LoadExData;
var
Buff, EOFBuff: Pointer;
BuffCount: Integer;
HeaderID, BlockSize: Word;
function GetOffset(Value: Integer): Pointer;
begin
Result := UIntToPtr(PtrToUInt(EOFBuff) - NativeUInt(Value));
end;
var
ExDataStream: TMemoryStream;
begin
if FFileHeader.Header.ExtraFieldLength = 0 then Exit;
GetMem(Buff, FFileHeader.Header.ExtraFieldLength);
try
BuffCount := FFileHeader.Header.ExtraFieldLength;
if FOwner.ZIPStream.Read(Buff^, BuffCount) <> BuffCount then
raise EZipReaderRead.CreateFmt(
'Îòñóòñòâóþò äàííûå ïîëÿ ExtraField ýëåìåíòà ¹%d "%s"', [ItemIndex, FileName]);
EOFBuff := UIntToPtr(PtrToUInt(Buff) + NativeUInt(BuffCount));
while BuffCount > 0 do
begin
HeaderID := PWord(GetOffset(BuffCount))^;
Dec(BuffCount, 2);
BlockSize := PWord(GetOffset(BuffCount))^;
Dec(BuffCount, 2);
case HeaderID of
SUPPORTED_EXDATA_ZIP64:
begin
{
-ZIP64 Extended Information Extra Field (0x0001):
===============================================
The following is the layout of the ZIP64 extended
information "extra" block. If one of the size or
offset fields in the Local or Central directory
record is too small to hold the required data,
a ZIP64 extended information record is created.
The order of the fields in the ZIP64 extended
information record is fixed, but the fields will
only appear if the corresponding Local or Central
directory record field is set to 0xFFFF or 0xFFFFFFFF.
Note: all fields stored in Intel low-byte/high-byte order.
Value Size Description
----- ---- -----------
(ZIP64) 0x0001 2 bytes Tag for this "extra" block type
Size 2 bytes Size of this "extra" block
Original
Size 8 bytes Original uncompressed file size
Compressed
Size 8 bytes Size of compressed data
Relative Header
Offset 8 bytes Offset of local header record
Disk Start
Number 4 bytes Number of the disk on which
this file starts
This entry in the Local header must include BOTH original
and compressed file sizes.
}
if FFileHeader.UncompressedSize = MAXDWORD then
begin
if BuffCount < 8 then Break;
FFileHeader.UncompressedSize := PInt64(GetOffset(BuffCount))^;
Dec(BuffCount, 8);
Dec(BlockSize, 8);
end;
if FFileHeader.CompressedSize = MAXDWORD then
begin
if BuffCount < 8 then Break;
FFileHeader.CompressedSize := PInt64(GetOffset(BuffCount))^;
Dec(BuffCount, 8);
Dec(BlockSize, 8);
end;
if FFileHeader.RelativeOffsetOfLocalHeader = MAXDWORD then
begin
if BuffCount < 8 then Break;
FFileHeader.RelativeOffsetOfLocalHeader := PInt64(GetOffset(BuffCount))^;
Dec(BuffCount, 8);
Dec(BlockSize, 8);
end;
if FFileHeader.DiskNumberStart = MAXWORD then
begin
if BuffCount < 4 then Break;
FFileHeader.DiskNumberStart := PCardinal(GetOffset(BuffCount))^;
Dec(BuffCount, 4);
Dec(BlockSize, 4);
end;
Dec(BuffCount, BlockSize);
Include(FPresentStreams, ssZIP64);
end;
SUPPORTED_EXDATA_NTFSTIME:
begin
{
-PKWARE Win95/WinNT Extra Field (0x000a):
=======================================
The following description covers PKWARE's "NTFS" attributes
"extra" block, introduced with the release of PKZIP 2.50 for
Windows. (Last Revision 20001118)
(Note: At this time the Mtime, Atime and Ctime values may
be used on any WIN32 system.)
[Info-ZIP note: In the current implementations, this field has
a fixed total data size of 32 bytes and is only stored as local
extra field.]
Value Size Description
----- ---- -----------
(NTFS) 0x000a Short Tag for this "extra" block type
TSize Short Total Data Size for this block
Reserved Long for future use
Tag1 Short NTFS attribute tag value #1
Size1 Short Size of attribute #1, in bytes
(var.) SubSize1 Attribute #1 data
.
.
.
TagN Short NTFS attribute tag value #N
SizeN Short Size of attribute #N, in bytes
(var.) SubSizeN Attribute #N data
For NTFS, values for Tag1 through TagN are as follows:
(currently only one set of attributes is defined for NTFS)
Tag Size Description
----- ---- -----------
0x0001 2 bytes Tag for attribute #1
Size1 2 bytes Size of attribute #1, in bytes (24)
Mtime 8 bytes 64-bit NTFS file last modification time
Atime 8 bytes 64-bit NTFS file last access time
Ctime 8 bytes 64-bit NTFS file creation time
The total length for this block is 28 bytes, resulting in a
fixed size value of 32 for the TSize field of the NTFS block.
The NTFS filetimes are 64-bit unsigned integers, stored in Intel
(least significant byte first) byte order. They determine the
number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch",
which is "01-Jan-1601 00:00:00 UTC".
}
// ïðîâåðÿåì ðàçìåðíîñòü ïîëÿ ñ ó÷åòîì ïðèìå÷àíèÿ:
// this field has a fixed total data size of 32 bytes
// åñëè ðàçìåð áóôôåðà ìåíüøå 32 áàéò - òî âûõîäèì èç ïðîöåäóðû
if BuffCount < 32 then Break;
// åñëè æå îí íå ðàâåð 32 áàéòàì,
// òî ïðîñòî ïðîïóñêàåì åãî è åðåõîäèì ê ñëåæóþùåé çàïèñè
if BlockSize <> 32 then
begin
Dec(BuffCount, BlockSize);
Continue;
end;
// ïðîïóñêàåì ïîëå Reserved
Dec(BuffCount, 4);
// Ïðîâåðÿåì ïîëå Tag
if PWord(GetOffset(BuffCount))^ <> 1 then
begin
Dec(BuffCount, BlockSize);
Continue;
end;
Dec(BuffCount, 2);
// Ïðîâåðÿåì ðàçìåð áëîêà äàííûõ
if PWord(GetOffset(BuffCount))^ <> SizeOf(TNTFSFileTime) then
begin
Dec(BuffCount, BlockSize);
Continue;
end;
Dec(BuffCount, 2);
// ×èòàåì ñàìè äàííûå
FFileHeader.Attributes.ftLastWriteTime := PFileTime(GetOffset(BuffCount))^;
Dec(BuffCount, SizeOf(TFileTime));
FFileHeader.Attributes.ftLastAccessTime := PFileTime(GetOffset(BuffCount))^;
Dec(BuffCount, SizeOf(TFileTime));
FFileHeader.Attributes.ftCreationTime := PFileTime(GetOffset(BuffCount))^;
Dec(BuffCount, SizeOf(TFileTime));
Include(FPresentStreams, ssNTFS);
end;
else
if Assigned(FOwner.OnLoadExData) then
begin
ExDataStream := TMemoryStream.Create;
try
ExDataStream.WriteBuffer(GetOffset(BuffCount)^, BlockSize);
ExDataStream.Position := 0;
FOwner.OnLoadExData(Self, FItemIndex, HeaderID, ExDataStream);
finally
ExDataStream.Free;
end;
end;
Dec(BuffCount, BlockSize);
end;
end;
finally
FreeMem(Buff);
end;
end;
//
// Ïðîöåäóðà çà÷èòûâàåò è ïðîâåðÿåò âàëèäíîñòü ñòðóêòóðû LocalFileHeader
// Çàäà÷à ïðîöåäóðû ïîëó÷èòü ïðàâèëüíîå çíà÷åíèå îôôñåòà íà íà÷àëî
// çàïàêîâàííîãî áëîêà äàííûõ.
// =============================================================================
procedure TFWZipReaderItem.LoadLocalFileHeader;
begin
// Rouse_ 02.10.2012
// Ïðè ÷òåíèè ó÷èòûâàåì îôôñåò íà íà÷àëî àðõèâà StartZipDataOffset
FOwner.SetStreamPosition(FFileHeader.DiskNumberStart,
FFileHeader.RelativeOffsetOfLocalHeader + FOwner.StartZipDataOffset);
if FOwner.ZIPStream.Read(FLocalFileHeader,
SizeOf(TLocalFileHeader)) <> SizeOf(TLocalFileHeader) then
raise EZipReaderRead.CreateFmt(
'Îòñóòñòñâóþò äàííûå TLocalFileHeader ýëåìåíòà ¹%d "%s"', [ItemIndex, FileName]);
if FLocalFileHeader.LocalFileHeaderSignature <>
LOCAL_FILE_HEADER_SIGNATURE then
raise EZipReaderItem.CreateFmt(
'Îøèáêà ÷òåíèÿ TLocalFileHeader ýëåìåíòà ¹%d "%s"', [ItemIndex, FileName]);
FFileHeader.DataOffset := FOwner.ZIPStream.Position +
FLocalFileHeader.FilenameLength + FLocalFileHeader.ExtraFieldLength;
end;
//
// Ïðîöåäóðà çà÷èòûâàåò ñòðîêîâîå çíà÷åíèå è ïåðåâîäèò åãî â Ansi ôîðìàò
// =============================================================================
procedure TFWZipReaderItem.LoadStringValue(var Value: string;
nSize: Cardinal; CheckEncoding: Boolean);
var
aString: AnsiString;
begin
if Integer(nSize) > 0 then
begin
{$IFDEF FPC}
aString := '';
{$ENDIF}
SetLength(aString, nSize);
if FOwner.ZIPStream.Read(aString[1], nSize) <> Integer(nSize) then
raise EZipReaderRead.CreateFmt(
'Îøèáêà ÷òåíèÿ ñòðîêîâûõ äàííûõ ýëåìåíòà ¹%d "%s"', [ItemIndex, FileName]);
// Rouse_ 13.06.2013
// 11 áèò îòâå÷àåò çà UTF8 êîäèðîâêó
if FFileHeader.Header.GeneralPurposeBitFlag and PBF_UTF8 = PBF_UTF8 then
begin
{$IFDEF UNICODE}
Value := string(UTF8ToUnicodeString(aString))
{$ELSE}
Value := string(UTF8Decode(aString));
// â íåþíèêîäíûõ âåðñèÿõ Delphi þíèêîäíûå ñèìâîëû áóäóò ïðåîáðàçîâàíû â çíàêè âîïðîñà
if CheckEncoding then
Value := StringReplace(Value, '?', '_', [rfReplaceAll]);
{$ENDIF}
end
else
Value := string(ConvertFromOemString(aString));
end;
end;
{ TFWZipReader }
//
// Ïðîöåäóðà ïðîèçâîäèò ïðîâåðêó àðõèâà ñ ó÷åòîì ìàñêè ôàéëà â àðõèâå
// Äàííûå ðàñïàêîâûâàþòñÿ, íî íå ñîõðàíÿþòñÿ
// =============================================================================
procedure TFWZipReader.Check(const ExtractMask: string);
begin
ProcessExtractOrCheckAllData(ExtractMask, '', True);
end;
//
// Ïðîöåäóðà î÷èùàåò äàííûå î îòêðûòîì ðàíåå àðõèâå
// =============================================================================
procedure TFWZipReader.Clear;
begin
ZeroMemory(@FZip64EOFCentralDirectoryRecord,
SizeOf(TZip64EOFCentralDirectoryRecord));
ZeroMemory(@FZip64EOFCentralDirectoryLocator,
SizeOf(TZip64EOFCentralDirectoryLocator));
ZeroMemory(@FEndOfCentralDir, SizeOf(TEndOfCentralDir));
FLocalFiles.Clear;
FreeAndNil(FFileStream);
end;
//
// Ôóíêöèÿ âîçâðàùàåò êîëè÷åñòâî ýëåìåíòîâ îòêðûòîãî àðõèâà
// =============================================================================
function TFWZipReader.Count: Integer;
begin
Result := FLocalFiles.Count;
end;
// =============================================================================
constructor TFWZipReader.Create;
begin
inherited;
FLocalFiles := TObjectList.Create;
FPasswordList := TStringList.Create;
FPasswordList.Duplicates := dupIgnore;
FPasswordList.Sorted := True;
DefaultDuplicateAction := daSkip;
end;
// =============================================================================
destructor TFWZipReader.Destroy;
begin
FPasswordList.Free;
FLocalFiles.Free;
FFileStream.Free;
inherited;
end;
//
// Ïðîöåäóðà âûçûâàåò îáðàáîò÷èê OnProgress
// =============================================================================
procedure TFWZipReader.DoProgress(Sender: TObject; const FileName: string;
Extracted, TotalSize: Int64; ProgressState: TProgressState);
var
Percent, TotalPercent: Byte;
Cancel: Boolean;
begin
if Assigned(FOnProgress) then
begin
if TotalSize = 0 then
if ProgressState in [psStart, psInitialization] then
Percent := 0
else
Percent := 100
else
if ProgressState = psEnd then
Percent := 100
else
Percent := Round(Extracted / (TotalSize / 100));
if FTotalSizeCount = 0 then
TotalPercent := 100
else
TotalPercent :=
Round((FTotalProcessedCount + Extracted) / (FTotalSizeCount / 100));
Cancel := False;
FOnProgress(Self, FileName, Percent, TotalPercent, Cancel, ProgressState);
if Cancel then Abort;
end;
end;
//
// Ïðîöåäóðà ïðîèçâîäèò àâòîìàòè÷åñêóþ ðàñïàêîâêó àðõèâà â óêàçàííóþ ïàïêó
// ñ ó÷åòîì ìàñêè ôàéëà â àðõèâå
// =============================================================================
procedure TFWZipReader.ExtractAll(const ExtractMask: string; Path: string);
begin
ProcessExtractOrCheckAllData(ExtractMask, Path, False);
end;
function TFWZipReader.Find(const Value: string; out AItem: TFWZipReaderItem;
IgnoreCase: Boolean): Boolean;
var
I: Integer;
AItemText: string;
begin
Result := False;
AItem := nil;
for I := 0 to Count - 1 do
begin
AItemText := Item[I].FileName;
if IgnoreCase then
Result := AnsiSameText(Value, AItemText)
else
Result := AnsiSameStr(Value, AItemText);
if Result then
begin
AItem := Item[I];
Break;
end;
end;;
end;
//
// Ïðîöåäóðà ïðîèçâîäèò àâòîìàòè÷åñêóþ ðàñïàêîâêó àðõèâà â óêàçàííóþ ïàïêó
// =============================================================================
procedure TFWZipReader.ExtractAll(const Path: string);
begin
ExtractAll('', Path);
end;
//
// Ôóíêöèÿ âîçâðàùàåò èíäåêñ ýëåìåíòà ïî åãî èìåíè
// =============================================================================
function TFWZipReader.GetElementIndex(const FileName: string): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to Count - 1 do
if {%H-}AnsiCompareText(Item[I].FileName, FileName) = 0 then
begin
Result := I;
Break;
end;
end;
//
// Ôóíêöèÿ âîçâðàùàåò ýëåìåíò àðõèâà ïî åãî èíäåêñó
// =============================================================================
function TFWZipReader.GetItem(Index: Integer): TFWZipReaderItem;
begin
Result := TFWZipReaderItem(FLocalFiles[Index]);
end;
//
// Ôóíêöèÿ äëÿ ïåðåêëþ÷åíèÿ ëîãèêè ðàáîòû ñ ìíîãîòîìíûìè ìàñèâàìè
// =============================================================================
function TFWZipReader.IsMultiPartZip: Boolean;
begin
Result := FZIPStream is TFWAbstractMultiStream;
end;
//
// Ïðîöåäóðà çà÷èòûâàåò öåíòðàëüíóþ äèðåêòîðèþ àðõèâà
// =============================================================================
procedure TFWZipReader.LoadCentralDirectoryFileHeader;
var
EndOfLoadCentralDirectory: Int64;
begin
EndOfLoadCentralDirectory := FZIPStream.Position + SizeOfCentralDirectory;
while FZIPStream.Position < EndOfLoadCentralDirectory do
FLocalFiles.Add(TFWZipReaderItem.InitFromStream(Self, Count));
// Rouse_ 01.11.2013
// Èñêëþ÷åíèå áóäåì ïîäíèìàòü òîëüêî â ñëó÷àå åñëè çàÿâëåííîå êîë-âî ýëåìåíòîâ
// áîëüøå ÷åì óäàëîñü ïðî÷èòàòü.
// Èáî ïîïàëñÿ ìíå îäèí àðõèâ â êîòîðîì êîë-âî ýëåìåíòîâ 95188,
// (ïðåâûøåíèå ïî êîëè÷åñòâó ýëåìåíòîâ è íóæíî èñïîëüçîâàòü ZIP64),
// íî ZIP64 íå èñïîëüçîâàëñÿ è ïîëå TotalNumberOfEntries õðàíèëî çíà÷åíèå 29652
// Ñîáñòâåííî ÷òî è ðàâíÿåòñÿ 95188 - $10000
// Ïîýòîìó âìåñòî òàêîãî óñëîâèÿ:
//if Count <> TotalEntryesCount then
//ïèøåì âîò òàê:
if Count < TotalEntryesCount then
raise EZipReader.CreateFmt(
'Îøèáêà ÷òåíèÿ öåíòðàëüíîé äèðåêòîðèè. ' + sLineBreak +
'Ïðî÷èòàííîå êîëè÷åñòâî ýëåìåíòîâ (%d) íå ñîîòâåòñòâóåò çàÿâëåííîìó (%d).',
[Count, TotalEntryesCount]);
end;
//
// Ïðîöåäóðà ïðîåðÿåò âàëèäíîñòü ñòðóêòóðû EndOfCentralDirectory
// Çàäà÷à ïðîöåäóðû ïîëó÷èòü îôôñåò íà íà÷àëî CentralDirectory
// =============================================================================
procedure TFWZipReader.LoadEndOfCentralDirectory;
var
Zip64LocatorOffset: Int64;
begin
// Ñîãëàñíî ñïåöèôèêàöèè â ñëó÷àå íàëè÷èÿ 64-áèòíûõ ñòðóêòóð
// TZip64EOFCentralDirectoryLocator èäåò ñðàçó ïåðåä EndOfCentralDirectory.
// Çàïîìèíàåì îôôñåò íà ïðåäïîëàãàåìóþ ïîçèöèþ äàííîé ñòðóêòóðû.
Zip64LocatorOffset := FZIPStream.Position -
SizeOf(TZip64EOFCentralDirectoryLocator);
if FZIPStream.Read(FEndOfCentralDir, SizeOf(TEndOfCentralDir)) <>
SizeOf(TEndOfCentralDir) then
raise EZipReader.Create('Îòñóòñòâóþò äàííûå ñòðóêòóðû TEndOfCentralDir.');
if (FEndOfCentralDir.NumberOfThisDisk <> 0) and not IsMultiPartZip then
raise EZipReader.Create('Çàãðóçêà ìíîãîòîìíûõ àðõèâîâ äîëæíà ïðîèçâîäèòñÿ ÷åðåç TFWAbstractMultiStream.');
if FEndOfCentralDir.EndOfCentralDirSignature <>
END_OF_CENTRAL_DIR_SIGNATURE then
raise EZipReader.Create('Îøèáêà ÷òåíèÿ ñòðóêòóðû TEndOfCentralDir.');
LoadStringValue(FEndOfCentralDirComment,
FEndOfCentralDir.ZipfileCommentLength);
{
6) If one of the fields in the end of central directory
record is too small to hold required data, the field
should be set to -1 (0xFFFF or 0xFFFFFFFF) and the
Zip64 format record should be created.
}
if (FEndOfCentralDir.NumberOfThisDisk = MAXWORD) or
(FEndOfCentralDir.DiskNumberStart = MAXWORD) or
(FEndOfCentralDir.TotalNumberOfEntriesOnThisDisk = MAXWORD) or
(FEndOfCentralDir.TotalNumberOfEntries = MAXWORD) or
(FEndOfCentralDir.SizeOfTheCentralDirectory = MAXDWORD) or
(FEndOfCentralDir.RelativeOffsetOfCentralDirectory = MAXDWORD) then
begin
// Îäíà èç ïîçèöèé íå ñîäåðæèò âàëèäíûõ äàííûõ
// Ñîãëàñíî ñïåöèôèêàöèè èõ íåîáõîäèìî ïîëó÷èòü ÷åðåç Zip64Locator
FZIPStream.Position := Zip64LocatorOffset + StartZipDataOffset;
LoadZIP64Locator;
end
else
// Rouse_ 02.10.2012
// Ïðè ÷òåíèè ó÷èòûâàåì îôôñåò íà íà÷àëî àðõèâà StartZipDataOffset
SetStreamPosition(FEndOfCentralDir.DiskNumberStart,
FEndOfCentralDir.RelativeOffsetOfCentralDirectory + StartZipDataOffset);
end;
//
// Ïðîöåäóðà îòêðûâàåò àðõèâ ïî óêàçàííîìó ïóòè
// =============================================================================
procedure TFWZipReader.LoadFromFile(const Value: string;
SFXOffset, ZipEndOffset: Integer);
begin
// Rouse_ 20.02.2012
// Åñëè TFileStream íå ñîçäàëñÿ FFileStream ìîæåò ñîäåðæàòü ðåô íà ðàçðóøåííûé TFileStream,
// ñîçäàííûé ïðè ïðåäûäóùåì âûçîâå LoadFromFile,
// ÷òî ïðèâåäåò ê îøèáêå â äåñòðóêòîðå ïðè ðàçðóøåíèè FFileStream
// Ñïàñèáî v1ctar çà íàéäåíûé ãëþê
//FFileStream.Free;
FreeAndNil(FFileStream);
FFileStream := TFileStream.Create(PathCanonicalize(Value), fmOpenRead or fmShareDenyWrite);
LoadFromStream(FFileStream, SFXOffset, ZipEndOffset);
end;
//
// Ïðîöåðóðà îòêðûâàåò àðõèâ èç ïåðåäàííîãî ñòðèìà
// =============================================================================
procedure TFWZipReader.LoadFromStream(Value: TStream;
SFXOffset, ZipEndOffset: Integer);
var
Buff: Pointer;
I, BuffSize, SignOffset: Integer;
Offset, EndOfCentralDirectoryOffset: Int64;
Cursor: PByte;
begin
FLocalFiles.Clear;
FZIPStream := Value;
// Rouse_ 02.10.2012
// Òåïåðü ìîãóò ïåðåäàâàòñÿ îôôñåòû íà ðàñïîëîæåíèå àðõèâà â ñòðèìå ñ äàííûìè
// SFXOffset óêàçûâàåò íà íà÷àëî àðõèâà
// ZipEndOffset óêàçûâàåò íà ïîçèöèþ ïîñëå êîòîðîé íå ïðîèçâîäèòñÿ ïîèñê
// ñèãíàòóðû EndOfCentralDir
// Íî ýòîò ôóíêöèîíàë íå ïîääåðæèâàåòñÿ äëÿ MultyPart àðõèâîâ
if IsMultiPartZip then
begin
SFXOffset := -1;
ZipEndOffset := -1;
end;
if SFXOffset < 0 then
FStartZipDataOffset := 0
else
FStartZipDataOffset := SFXOffset;
if ZipEndOffset < 0 then
FEndZipDataOffset := Value.Size
else
FEndZipDataOffset := ZipEndOffset;
// Èùåì ñèãíàòóðó EndOfCentralDir
BuffSize := $FFFF;
// Rouse_ 13.03.2015
// Åñëè àðõèâ ïóñòîé, òî END_OF_CENTRAL_DIR_SIGNATURE áóäåò ðàñïîëîãàòüñÿ
// ïî íóëåâîìó îôôñåòó, ñòàëî áûòü íîëü - ýòî òîæå ïðàâèëüíîå çíà÷åíèå
// Ïîýòîìó ôëàã îòñóòñòâèÿ äàííîãî ìàðêåðà áóäåò íå íîëü, à îòðèöàòåëüíîå çíà÷åíèå
EndOfCentralDirectoryOffset := -1;
//EndOfCentralDirectoryOffset := 0;
Offset := EndZipDataOffset;
SignOffset := 0;
GetMem(Buff, BuffSize);
try
while Offset > StartZipDataOffset do
begin
{%H-}Dec(Offset, BuffSize - SignOffset);
if Offset < StartZipDataOffset then
begin
Inc(BuffSize, Offset - StartZipDataOffset);
Offset := StartZipDataOffset;
end;
Value.Position := Offset;
if Value.Read(Buff^, BuffSize) <> BuffSize then
raise EZipReaderRead.Create('Îøèáêà ÷òåíèÿ äàííûõ ïðè ïîèñêå END_OF_CENTRAL_DIR_SIGNATURE');
// Rouse_ 14.02.2013
// Åñëè â àðõèâå áóäåò íåçàïàêîâàíûé ZIP àðõèâ,
// òî åñòü áîëüøîé øàíñ ÷òî ïåðâóþ END_OF_CENTRAL_DIR_SIGNATURE ìû
// îáíàðóæèì ó íåãî, à íå ó íàøåãî àðõèâà
{
Cursor := Buff;
for I := 0 to BuffSize - 1 do
begin
if PCardinal(Cursor)^ = END_OF_CENTRAL_DIR_SIGNATURE then
begin
EndOfCentralDirectoryOffset := Offset + I;
Break;
end
else
Inc(Cursor);
}
// ïîýòîìó ñèãíàòóðó END_OF_CENTRAL_DIR_SIGNATURE áóäåì èñêàòü âîò òàê
Cursor := PByte(PAnsiChar(Buff) + BuffSize - 5);
for I := BuffSize - 5 downto 0 do
begin
if PCardinal(Cursor)^ = END_OF_CENTRAL_DIR_SIGNATURE then
begin
EndOfCentralDirectoryOffset := Offset + I;
Break;
end
else
Dec(Cursor);
end;
if EndOfCentralDirectoryOffset >= 0 then
Break;
// Rouse_ 14.02.2013
// Ñèãíàòóðà ìîæåò ðàñïîëàãàòüñÿ íà ãðàíèöå ìåæäó äâóìÿ áóôåðàìè
// ïîýòîìó ÷òîáû ñ÷èòàòü ãðàíè÷íîå ñîñòîÿíèå äåëàåì ïîïðàâêó
SignOffset := 4;
end;
finally
FreeMem(Buff);
end;
if EndOfCentralDirectoryOffset < 0 then
raise EZipReader.Create('Íå íàéäåíà ñèãíàòóðà END_OF_CENTRAL_DIR_SIGNATURE.');
// Çà÷èòûâàåì ñàìó ñòðóêòóðó EndOfCentralDirectory
// Ïðè íåîáõîäèìîñòè áóäóò çà÷èòàíû äàííûå èç 64 áèòíûõ ñòðóêòóð
Value.Position := EndOfCentralDirectoryOffset;
LoadEndOfCentralDirectory;
// Òåïåðü óêàçàòåëü ñòðèìà âûñòàâëåí íà íà÷àëî ñòðóêòóðû CentralDirectory
// Çà÷èòûâàåì åå ñàìó
LoadCentralDirectoryFileHeader;
end;
//
// Ïðîöåäóðà çà÷èòûâàåò ñòðîêîâîå çíà÷åíèå è ïåðåâîäèò åãî â Ansi ôîðìàò
// =============================================================================
procedure TFWZipReader.LoadStringValue(var Value: AnsiString; nSize: Cardinal);
begin
if Integer(nSize) > 0 then
begin
SetLength(Value, nSize);
if FZIPStream.Read(Value[1], nSize) <> Integer(nSize) then
raise EZipReaderRead.Create('Îøèáêà ÷òåíèÿ êîìåíòàðèÿ ê àðõèâó');
Value := ConvertFromOemString(Value);
end;
end;
//
// Ïðîöåäóðà ïðîâåðÿåò âàëèäíîñòü ñòðóêòóðû Zip64EOFCentralDirectoryRecord
// Çàäà÷à ïðîöåäóðó ïîëó÷èòü îôôñåò íà CentralDirectory
// =============================================================================
procedure TFWZipReader.LoadZip64EOFCentralDirectoryRecord;
begin
FZIPStream.ReadBuffer(FZip64EOFCentralDirectoryRecord,
SizeOf(TZip64EOFCentralDirectoryRecord));
if not Zip64Present then
raise EZipReader.Create(
'Îøèáêà ÷òåíèÿ ñòðóêòóðû TZip64EOFCentralDirectoryRecord');
// Rouse_ 02.10.2012
// Ïðè ÷òåíèè ó÷èòûâàåì îôôñåò íà íà÷àëî àðõèâà StartZipDataOffset
SetStreamPosition(FZip64EOFCentralDirectoryRecord.DiskNumberStart,
FZip64EOFCentralDirectoryRecord.RelativeOffsetOfCentralDirectory +
StartZipDataOffset);
end;
//
// Ïðîöåäóðà ïðîâåðÿåò âàëèäíîñòü ñòðóêòóðû ZIP64Locator
// Çàäà÷à ïðîöåäóðó ïîëó÷èòü îôôñåò íà Zip64EOFCentralDirectoryRecord
// =============================================================================
procedure TFWZipReader.LoadZIP64Locator;
begin
FZIPStream.ReadBuffer(FZip64EOFCentralDirectoryLocator,
SizeOf(TZip64EOFCentralDirectoryLocator));
if FZip64EOFCentralDirectoryLocator.Signature <>
ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE then
raise EZipReader.Create(
'Îøèáêà ÷òåíèÿ ñòðóêòóðû TZip64EOFCentralDirectoryLocator');
// Äàííàÿ ñòðóêòóðà õðàíèò îôôñåò íà TZip64EOFCentralDirectoryRecord
// Â êîòîðîì è õðàíèòüñÿ ðàñøèðåííàÿ èíôîðìàöèÿ
SetStreamPosition(FZip64EOFCentralDirectoryLocator.DiskNumberStart,
FZip64EOFCentralDirectoryLocator.RelativeOffset + StartZipDataOffset);
LoadZip64EOFCentralDirectoryRecord;
end;
//
// Ïðîöåäóðà ïðîèçâîäèò ðàñïàêîâêó èëè ïðîâåðêó àðõèâà ñ ó÷åòîì ìàñêè ôàéëà â àðõèâå
// Ïðè ïðîâåðêå àðõèâà äàííûå ðàñïàêîâûâàþòñÿ, íî íå ñîõðàíÿþòñÿ
// =============================================================================
procedure TFWZipReader.ProcessExtractOrCheckAllData(const ExtractMask: string;
Path: string; CheckMode: Boolean);
var
I, A: Integer;
OldExtractEvent: TZipExtractItemEvent;
OldDuplicateEvent: TZipDuplicateEvent;
CurrentItem: TFWZipReaderItem;
ExtractResult: TExtractResult;
CancelExtract, Handled: Boolean;
Password: string;
ExtractList: TList;
FakeStream: TFakeStream;
begin
FTotalSizeCount := 0;
FTotalProcessedCount := 0;
ExtractList := TList.Create;
try
// Ïðîèçâîäèì ïîèñê ôàéëîâ äëÿ ðàñïàêîâêè
for I := 0 to Count - 1 do
if ExtractMask = '' then
begin
ExtractList.Add(UIntToPtr(I));
Inc(FTotalSizeCount, Item[I].UncompressedSize);
end
else
if MatchesMask(Item[I].FileName, ExtractMask) then
begin
ExtractList.Add(UIntToPtr(I));
Inc(FTotalSizeCount, Item[I].UncompressedSize);
end;
if not CheckMode then
begin
// Ïðàâêà ïóñòîãî è îòíîñèòåëüíîãî ïóòè
Path := PathCanonicalize(Path);
if Path = '' then
Path := GetCurrentDir;
// Ïðîâåðêà õâàòèò ëè ìåñòà íà äèñêå?
if GetDiskFreeAvailable(PChar(Path)) <= FTotalSizeCount then
raise EZipReader.CreateFmt('Íåäîñòàòî÷íî ìåñòà íà äèñêå "%s".' + sLineBreak +
'Íåîáõîäèìî îñâîáîäèòü %s.',
[Path{$IFDEF MSWINDOWS}[1]{$ENDIF},
FileSizeToStr(FTotalSizeCount)]);
end;
FakeStream := TFakeStream.Create;
try
for I := 0 to ExtractList.Count - 1 do
begin
FakeStream.Size := 0;
CurrentItem := Item[Integer(PtrToUInt(ExtractList[I]))];
DoProgress(Self, CurrentItem.FileName, 0, CurrentItem.UncompressedSize, psStart);
OldExtractEvent := CurrentItem.OnProgress;
try
CurrentItem.OnProgress := DoProgress;
OldDuplicateEvent := CurrentItem.OnDuplicate;
try
CurrentItem.OnDuplicate := OnDuplicate;
// Ïðîáóåì èçâëå÷ü ôàéë
try
if CheckMode then
ExtractResult := CurrentItem.ExtractToStream(FakeStream, '')
else
ExtractResult := CurrentItem.Extract(Path, '');
if ExtractResult = erNeedPassword then
begin
// Åñëè ïðîèçîøëà îáøèáêà èç-çà òîãî ÷òî ôàéë çàøèôðîâàí,
// ïðîáóåì ðàñøèôðîâàòü åãî èñïîëüçóÿ ñïèñîê èçâåñòíûõ ïàðîëåé
for A := 0 to FPasswordList.Count - 1 do
begin
if CheckMode then
ExtractResult := CurrentItem.ExtractToStream(FakeStream, FPasswordList[A])
else
ExtractResult := CurrentItem.Extract(Path, FPasswordList[A]);
if ExtractResult in [erDone, erSkiped] then Break;
end;
// åñëè íå ïîëó÷èëîñü, çàïðàøèâàåì ïàðîëü ó ïîëüçîâàòåëÿ
if ExtractResult = erNeedPassword then
if Assigned(FOnNeedPwd) then
begin
CancelExtract := False;
while ExtractResult = erNeedPassword do
begin
Password := '';
FOnNeedPwd(Self, CurrentItem.FileName,
Password, CancelExtract);
if CancelExtract then Exit;
if Password <> '' then
begin
FPasswordList.Add(Password);
if CheckMode then
ExtractResult := CurrentItem.ExtractToStream(FakeStream, Password)
else
ExtractResult := CurrentItem.Extract(Path, Password);
end;
end;
end
else
raise EWrongPasswordException.CreateFmt(
'Îøèáêà èçâëå÷åíèÿ äàííûõ ýëåìåíòà ¹%d "%s".' + sLineBreak +
'Íåâåðíûé ïàðîëü.', [CurrentItem.ItemIndex, CurrentItem.FileName]);
end;
except
// Ïîëüçîâàòåëü îòìåíèë ðàñïàêîâêó àðõèâà
on E: EAbort do
Exit;
// Íó íå ïðåðûâàòü æå ðàñïàêîâêó èç-çà èñêëþ÷åíèÿ íà îäíîì ôàéëå?
// Ïóñòü ðåøåíèå î ïðåðûâàíèè ðàñïàêîâêè ïðèíèìàþò ñíàðóæè
on E: Exception do
begin
Handled := False;
if Assigned(FException) then
FException(Self, E, Integer(PtrToUInt(ExtractList[I])), Handled);
if not Handled then
// Rouse_ 20.02.2012
// Íåâåðíî ïåðåâîçáóæäåíî èñêëþ÷åíèå
// Ñïàñèáî v1ctar çà íàéäåíûé ãëþê
//raise E;
raise;
end;
end;
Inc(FTotalProcessedCount, CurrentItem.UncompressedSize);
finally
CurrentItem.OnDuplicate := OldDuplicateEvent;
end;
finally
CurrentItem.OnProgress := OldExtractEvent;
DoProgress(Self, CurrentItem.FileName, 0,
CurrentItem.UncompressedSize, psEnd);
end;
end;
finally
FakeStream.Free;
end;
finally
ExtractList.Free;
end;
end;
procedure TFWZipReader.SetDefaultDuplicateAction(const Value: TDuplicateAction);
begin
if Value = daUseNewFilePath then
raise EZipReader.Create(
'Äåéñòâèå daUseNewFilePath ìîæíî íàçíà÷àòü òîëüêî â îáðàáîò÷èêå ñîáûòèÿ OnDuplicate.');
FDefaultDuplicateAction := Value;
end;
procedure TFWZipReader.SetStreamPosition(DiskNumber: Integer; Offset: Int64);
begin
if IsMultiPartZip then
TFWAbstractMultiStream(FZIPStream).Seek(DiskNumber, Offset)
else
FZIPStream.Position := Offset;
end;
//
// Ôóíêöèÿ âîçâðàùàåò ðàçìåð öåíòðàëüíîé äèðåêòîðèè
// =============================================================================
function TFWZipReader.SizeOfCentralDirectory: Int64;
begin
if Zip64Present then
Result := FZip64EOFCentralDirectoryRecord.SizeOfTheCentralDirectory
else
Result := FEndOfCentralDir.SizeOfTheCentralDirectory;
end;
//
// Ôóíêöèÿ âîçâðàùàåò êîëè÷åñòâî ýëåìåíòîâ àðõèâà
// =============================================================================
function TFWZipReader.TotalEntryesCount: Integer;
begin
if Zip64Present then
Result := FZip64EOFCentralDirectoryRecord.TotalNumberOfEntries
else
Result := FEndOfCentralDir.TotalNumberOfEntries;
end;
//
// Âñïîìîãàòåëüíàÿ ôóíêöèÿ,
// óêàçûâàåò èç êàêîãî áëîêà äàííûõ áðàòü âàëèäíîå çíà÷åíèå
// =============================================================================
function TFWZipReader.Zip64Present: Boolean;
begin
Result := FZip64EOFCentralDirectoryRecord.Zip64EndOfCentralDirSignature =
ZIP64_END_OF_CENTRAL_DIR_SIGNATURE
end;
end.