Работа с ZIP-архивами в Delphi без использования Abrevia: Объединение и валидация
В мире Delphi и Pascal, работа с ZIP-архивами часто требует использования сторонних библиотек. Однако, существуют ситуации, когда необходимо обойтись без них. В этой статье мы рассмотрим, как можно добавлять файлы в существующие ZIP-архивы и валидировать их структуру, используя только стандартные средства Object Pascal.
Проблема:
Традиционный TZipper не предоставляет встроенной возможности добавления файлов в существующий ZIP-архив. Стандартный подход заключается в создании нового архива, что может быть неэффективно при работе с большими объемами данных.
Решение:
Основываясь на предложенном geraldholdsworth решении, мы можем обойти это ограничение, манипулируя структурой ZIP-файла напрямую. ZIP-файл представляет собой последовательность сжатых файлов, каждый из которых имеет свою запись в центральном каталоге. Таким образом, добавление файла сводится к:
Сжатию нового файла и созданию его локальной записи.
Добавлению локальной записи и сжатых данных в конец существующего архива.
Обновлению центрального каталога (Central Directory) и записи конца центрального каталога (End of Central Directory - EOCD).
Реализация объединения ZIP-файлов (с небольшими изменениями):
Представленный ниже код демонстрирует объединение двух ZIP-архивов в один. Этот подход можно адаптировать для добавления отдельных файлов в существующий архив.
unit ZIPTools;
interface
uses
Classes, SysUtils;
function FindCL(var EoCL: Integer; var buffer: array of Byte): Integer;
function ValidateZIPFile(var buffer: array of Byte): Boolean;
function CombineZIP(files: array of String; outputfile: String): Boolean;
implementation
//Находит начало центрального каталога и конец записи центрального каталога
function FindCL(var EoCL: Integer; var buffer: array of Byte): Integer;
var
i: Integer;
begin
Result := -1;
EoCL := -1;
if Length(buffer) < 22 then Exit;
i := Length(buffer) - 3;
repeat
Dec(i);
until ((buffer[i] = $50) and (buffer[i + 1] = $4B) and (buffer[i + 2] = $05) and
(buffer[i + 3] = $06)) or (i = 0);
if (buffer[i] = $50) and (buffer[i + 1] = $4B) and (buffer[i + 2] = $05) and
(buffer[i + 3] = $06) then
begin
EoCL := i;
Result := buffer[i + $10] + (buffer[i + $11] shl 8) + (buffer[i + $12] shl 16) +
(buffer[i + $13] shl 24);
end;
end;
//Валидирует ZIP-файл
function ValidateZIPFile(var buffer: array of Byte): Boolean;
var
CL: Integer;
EoCL: Integer;
Check: Boolean;
Temp: Cardinal;
NumFiles: Integer;
Index: Integer;
Ptr: Cardinal;
Data: Cardinal;
Size: Cardinal;
USize: Cardinal;
function CheckExtraField(addr, len: Cardinal): Boolean;
var
Total: Cardinal;
begin
Result := False;
Total := 0;
while (Total < len) and (addr < Length(buffer) - 4) do
begin
Inc(Total, 4 + buffer[addr + 2] + (buffer[addr + 3] shl 8));
Inc(addr, 4 + buffer[addr + 2] + (buffer[addr + 3] shl 8));
end;
Result := Total = len;
end;
begin
Result := False;
if Length(buffer) >= 22 then
begin
CL := FindCL(EoCL, buffer);
if (CL <> -1) and (EoCL <> -1) then
begin
Result := True;
Temp := buffer[EoCL + $C] + (buffer[EoCL + $D] shl 8) + (buffer[EoCL + $E] shl 16) +
(buffer[EoCL + $F] shl 24);
Result := (Temp = EoCL - CL) and Result;
NumFiles := buffer[EoCL + $A] + (buffer[EoCL + $B] shl 8);
Ptr := CL;
Index := 0;
while (Index < NumFiles) and (Ptr < EoCL) do
begin
Result := (buffer[Ptr] = $50) and (buffer[Ptr + 1] = $4B) and
(buffer[Ptr + 2] = $01) and (buffer[Ptr + 3] = $02) and Result;
Temp := buffer[Ptr + $1E] + (buffer[Ptr + $1F] shl 8);
if Temp > 0 then
Result := (CheckExtraField(Ptr + $2E + buffer[Ptr + $1C] + (buffer[Ptr + $1D] shl 8),
Temp)) and Result;
Size := buffer[Ptr + $14] + (buffer[Ptr + $15] shl 8) + (buffer[Ptr + $16] shl 16) +
(buffer[Ptr + $17] shl 24);
USize := buffer[Ptr + $18] + (buffer[Ptr + $19] shl 8) + (buffer[Ptr + $1A] shl 16) +
(buffer[Ptr + $1B] shl 24);
Data := buffer[Ptr + $2A] + (buffer[Ptr + $2B] shl 8) + (buffer[Ptr + $2C] shl 16) +
(buffer[Ptr + $2D] shl 24);
Result := (buffer[Data] = $50) and (buffer[Data + 1] = $4B) and
(buffer[Data + 2] = $03) and (buffer[Data + 3] = $04) and Result;
Result := (buffer[Data + $12] + (buffer[Data + $13] shl 8) +
(buffer[Data + $14] shl 16) + (buffer[Data + $15] shl 24) = Size) and Result;
Result := (buffer[Data + $16] + (buffer[Data + $17] shl 8) +
(buffer[Data + $18] shl 16) + (buffer[Data + $19] shl 24) = USize) and Result;
Temp := buffer[Data + $1C] + (buffer[Data + $1D] shl 8);
if Temp > 0 then
Result := (CheckExtraField(Data + $1E + buffer[Data + $1A] +
(buffer[Data + $1B] shl 8), Temp)) and Result;
Temp := Ptr + $2E + buffer[Ptr + $1C] + (buffer[Ptr + $1D] shl 8) +
buffer[Ptr + $1E] + (buffer[Ptr + $1F] shl 8) + buffer[Ptr + $20] +
(buffer[Ptr + $21] shl 8);
repeat
Inc(Ptr);
until (Ptr = EoCL) or
((buffer[Ptr] = $50) and (buffer[Ptr + 1] = $4B) and
(buffer[Ptr + 2] = $01) and (buffer[Ptr + 3] = $02));
Result := (Ptr = Temp) and Result;
Inc(Index);
end;
Result := (Index = NumFiles) and Result;
end;
end;
end;
//Объединяет два ZIP-файла
function CombineZIP(files: array of String; outputfile: String): Boolean;
var
Input: array[0..1] of TFileStream;
Output: TFileStream;
InBuffer: array[0..1] of array of Byte;
OutBuffer: array of Byte;
Ptr: Integer;
Cnt: Integer;
FilePtr: Cardinal;
CL: array[0..1] of Integer;
EoCL: array[0..1] of Integer;
Temp: Cardinal;
NumFiles: Cardinal;
CLSize: Cardinal;
FileSize: Cardinal;
begin
Result := False;
if Length(files) >= 2 then
begin
Result := True;
for Ptr := 0 to 1 do
begin
Input[Ptr] := TFileStream.Create(files[Ptr], fmOpenRead or fmShareDenyNone);
Input[Ptr].Position := 0;
SetLength(InBuffer[Ptr], Input[Ptr].Size);
Input[Ptr].Read(InBuffer[Ptr][0], Input[Ptr].Size);
Input[Ptr].Free;
CL[Ptr] := FindCL(EoCL[Ptr], InBuffer[Ptr]);
Result := Result and (CL[Ptr] <> -1) and (EoCL[Ptr] <> -1);
if EoCL[Ptr] <> -1 then
Inc(NumFiles, InBuffer[Ptr][EoCL[Ptr] + $A] + (InBuffer[Ptr][EoCL[Ptr] + $B] shl 8));
end;
if Result then
begin
CLSize := (EoCL[0] - CL[0]) + (EoCL[1] - CL[1]);
FileSize := CL[0] + CL[1] + CLSize + 22;
SetLength(OutBuffer, FileSize);
FilePtr := 0;
for Cnt := 0 to 1 do
begin
Move(InBuffer[Cnt][0], OutBuffer[FilePtr], CL[Cnt]);
Inc(FilePtr, CL[Cnt]);
end;
for Cnt := 0 to 1 do
begin
for Ptr := CL[Cnt] to EoCL[Cnt] - 1 do
begin
OutBuffer[FilePtr - CL[Cnt] + Ptr] := InBuffer[Cnt][Ptr];
if Cnt > 0 then
begin
if (InBuffer[Cnt][Ptr - $2E] = $50) and (InBuffer[Cnt][Ptr - $2D] = $4B) and
(InBuffer[Cnt][Ptr - $2C] = $01) and (InBuffer[Cnt][Ptr - $2B] = $02) then
begin
Temp := InBuffer[Cnt][Ptr - 4] + (InBuffer[Cnt][Ptr - 3] shl 8) +
(InBuffer[Cnt][Ptr - 2] shl 16) + (InBuffer[Cnt][Ptr - 1] shl 24);
Inc(Temp, CL[0]); // Корректировка смещения
OutBuffer[FilePtr - CL[Cnt] + Ptr - 4] := Temp and $000000FF;
OutBuffer[FilePtr - CL[Cnt] + Ptr - 3] := (Temp and $0000FF00) shr 8;
OutBuffer[FilePtr - CL[Cnt] + Ptr - 2] := (Temp and $00FF0000) shr 16;
OutBuffer[FilePtr - CL[Cnt] + Ptr - 1] := (Temp and $FF000000) shr 24;
end;
end;
end;
Inc(FilePtr, EoCL[Cnt] - CL[Cnt]); // Используем EoCL[cnt]-CL[cnt]
end;
FilePtr := FileSize - 22;
OutBuffer[FilePtr] := $50;
OutBuffer[FilePtr + $01] := $4B;
OutBuffer[FilePtr + $02] := $05;
OutBuffer[FilePtr + $03] := $06;
OutBuffer[FilePtr + $08] := NumFiles and $00FF;
OutBuffer[FilePtr + $09] := (NumFiles and $FF00) shr 8;
OutBuffer[FilePtr + $0A] := NumFiles and $00FF;
OutBuffer[FilePtr + $0B] := (NumFiles and $FF00) shr 8;
OutBuffer[FilePtr + $0C] := CLSize and $000000FF;
OutBuffer[FilePtr + $0D] := (CLSize and $0000FF00) shr 8;
OutBuffer[FilePtr + $0E] := (CLSize and $00FF0000) shr 16;
OutBuffer[FilePtr + $0F] := (CLSize and $FF000000) shr 24;
OutBuffer[FilePtr + $10] := (CL[0] + CL[1]) and $000000FF;
OutBuffer[FilePtr + $11] := ((CL[0] + CL[1]) and $0000FF00) shr 8;
OutBuffer[FilePtr + $12] := ((CL[0] + CL[1]) and $00FF0000) shr 16;
OutBuffer[FilePtr + $13] := ((CL[0] + CL[1]) and $FF000000) shr 24;
Output := TFileStream.Create(outputfile, fmCreate or fmShareDenyNone);
Output.Position := 0;
Output.Write(OutBuffer[0], Length(OutBuffer));
Output.Free;
end;
end;
end;
end.
Ключевые моменты в коде:
FindCL: Ищет начало центрального каталога и конец записи центрального каталога (EOCD) в ZIP-файле.
ValidateZIPFile: Валидирует структуру ZIP-файла, проверяя сигнатуры, размеры и смещения. Этот метод может быть ресурсоемким для больших архивов.
CombineZIP: Объединяет два ZIP-файла в один. Важно отметить, что этот код предполагает отсутствие ZIP64 и многодисковых архивов.
Корректировка смещений: При объединении центральных каталогов необходимо корректировать смещения файлов, чтобы они указывали на правильные позиции в объединенном архиве. Это делается путем добавления смещения первого архива к смещениям файлов во втором архиве.
Move: Используется для быстрого копирования блоков памяти.
Альтернативное решение (частичная декомпрессия):
Более надежный, но и более сложный подход заключается в частичной декомпрессии существующего архива. Этот метод включает в себя:
Чтение центрального каталога и извлечение информации о файлах.
Декомпрессию файлов, которые необходимо заменить или удалить.
Добавление новых файлов и их сжатие.
Создание нового центрального каталога и записи EOCD.
Этот подход требует более глубокого понимания формата ZIP и алгоритмов сжатия, но обеспечивает большую гибкость и надежность.
Пример использования:
uses
ZIPTools;
var
Files: array[0..1] of String;
OutputFile: String;
begin
Files[0] := 'archive1.zip';
Files[1] := 'archive2.zip';
OutputFile := 'combined.zip';
if CombineZIP(Files, OutputFile) then
ShowMessage('Архивы успешно объединены в ' + OutputFile)
else
ShowMessage('Ошибка при объединении архивов');
end;
Валидация ZIP-файла:
uses
ZIPTools;
var
Buffer: array of Byte;
FileStream: TFileStream;
FileName: String;
begin
FileName := 'archive.zip';
FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
try
SetLength(Buffer, FileStream.Size);
FileStream.Read(Buffer[0], FileStream.Size);
if ValidateZIPFile(Buffer) then
ShowMessage('Файл является валидным ZIP-архивом')
else
ShowMessage('Файл не является валидным ZIP-архивом');
finally
FileStream.Free;
end;
end;
В статье описывается метод работы с ZIP-архивами в Delphi без использования сторонних библиотек, позволяющий добавлять файлы и валидировать структуру архива путем манипулирования его структурой.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.