Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Как работать с ZIP-файлами в Delphi без использования Abrevia

Delphi , Файловая система , Файлы

Работа с ZIP-архивами в Delphi без использования Abrevia: Объединение и валидация

В мире Delphi и Pascal, работа с ZIP-архивами часто требует использования сторонних библиотек. Однако, существуют ситуации, когда необходимо обойтись без них. В этой статье мы рассмотрим, как можно добавлять файлы в существующие ZIP-архивы и валидировать их структуру, используя только стандартные средства Object Pascal.

Проблема:

Традиционный TZipper не предоставляет встроенной возможности добавления файлов в существующий ZIP-архив. Стандартный подход заключается в создании нового архива, что может быть неэффективно при работе с большими объемами данных.

Решение:

Основываясь на предложенном geraldholdsworth решении, мы можем обойти это ограничение, манипулируя структурой ZIP-файла напрямую. ZIP-файл представляет собой последовательность сжатых файлов, каждый из которых имеет свою запись в центральном каталоге. Таким образом, добавление файла сводится к:

  1. Сжатию нового файла и созданию его локальной записи.
  2. Добавлению локальной записи и сжатых данных в конец существующего архива.
  3. Обновлению центрального каталога (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: Используется для быстрого копирования блоков памяти.

Альтернативное решение (частичная декомпрессия):

Более надежный, но и более сложный подход заключается в частичной декомпрессии существующего архива. Этот метод включает в себя:

  1. Чтение центрального каталога и извлечение информации о файлах.
  2. Декомпрессию файлов, которые необходимо заменить или удалить.
  3. Добавление новых файлов и их сжатие.
  4. Создание нового центрального каталога и записи 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;

Преимущества и недостатки подхода без Abrevia:

  • Преимущества:
    • Отсутствие зависимостей от сторонних библиотек.
    • Более глубокое

Создано по материалам из источника по ссылке.

В статье описывается метод работы с ZIP-архивами в Delphi без использования сторонних библиотек, позволяющий добавлять файлы и валидировать структуру архива путем манипулирования его структурой.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: Файлы ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-09-05 12:42:20/0.0068519115447998/0