{
ReadZIP содержит класс для чтения архивов в формате ZIP.
Copyright © 2016, 2019, 2022–2023 Малик Разработчик
Это свободная программа: вы можете перераспространять её и/или
изменять её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она может быть полезна,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<http://www.gnu.org/licenses/>.
}
unit ReadZIP;
{$MODE DELPHI}
interface
uses
Lang,
IOStreams,
Zlib;
{%region public }
type
ZIPArchiveReader = class(_Object)
strict private
onEnd: boolean;
start: long;
archive: DataInput;
procedure checkEnd();
function getFileSizes(): long;
public
constructor create(source: Input);
procedure gotoFirstFile();
procedure gotoNextFile();
function isStayOnEnd(): boolean;
function openFile(const fileName: AnsiString): Input;
function openCurrentFile(): Input;
function getCurrentFileName(): AnsiString;
strict private const
INFO_TYPE_FILE = int($04034b50);
INFO_TYPE_FILE_DESCRIPTOR = int($08074b50);
INFO_TYPE_EXTRA_DATA = int($08064b50);
INFO_TYPE_CENTRAL_DIRECTORY = int($02014b50);
INFO_TYPE_DIGITAL_SIGNATURE = int($05054b50);
INFO_TYPE_END_OF_CENTRAL_DIRECTORY = int($06054b50);
DESCRIPTOR_SIZE = int($10);
METHOD_NO_COMPRESSION = int(0);
METHOD_DEFLATED = int(8);
class function ISO88591toUTF8(const fileName: AnsiString): AnsiString; static;
end;
{%endregion}
implementation
{%region ZIPArchiveReader }
procedure ZIPArchiveReader.checkEnd();
var
archive: DataInput;
i: int;
begin
archive := self.archive;
try
while archive.available() > 0 do begin
case archive.readIntLE() of
INFO_TYPE_FILE: begin
onEnd := false;
exit;
end;
INFO_TYPE_FILE_DESCRIPTOR: begin
archive.seek($0c);
end;
INFO_TYPE_EXTRA_DATA: begin
i := archive.readIntLE();
archive.seek(zeroExtend(i));
end;
INFO_TYPE_CENTRAL_DIRECTORY: begin
archive.seek($18);
i := archive.readUnsignedShortLE() + archive.readUnsignedShortLE() + archive.readUnsignedShortLE() + $0c;
archive.seek(long(i));
end;
INFO_TYPE_DIGITAL_SIGNATURE: begin
i := archive.readUnsignedShortLE();
archive.seek(long(i));
end;
INFO_TYPE_END_OF_CENTRAL_DIRECTORY: begin
archive.seek($10);
i := archive.readUnsignedShortLE();
archive.seek(long(i));
end;
else
break;
end;
end;
onEnd := true;
except
on e: IOException do begin
onEnd := true;
end;
else begin
raise;
end;
end;
end;
function ZIPArchiveReader.getFileSizes(): long;
var
archive: DataInput;
readed: int;
count: int;
i: int;
s: int;
bytes: byte_Array1d;
begin
result := 0;
archive := self.archive;
bytes := byte_Array1d_create(2 * DESCRIPTOR_SIZE);
readed := archive.read(bytes, 0, DESCRIPTOR_SIZE);
if readed < DESCRIPTOR_SIZE then begin
raise EOFException.create('ZIPArchiveReader: архив повреждён или имеет неправильный формат.');
end;
repeat
count := archive.read(bytes, DESCRIPTOR_SIZE, DESCRIPTOR_SIZE);
if count < 0 then begin
count := 0;
end;
inc(readed, count);
for i := 0 to count do begin
s := i + readed - count - DESCRIPTOR_SIZE;
if (int((@(bytes[i]))^) = INFO_TYPE_FILE_DESCRIPTOR) and (int((@(bytes[i + $08]))^) = s) then begin
archive.seek(long(-readed));
result := buildLong(int((@(bytes[i + $08]))^), int((@(bytes[i + $0c]))^));
exit;
end;
end;
if count = 0 then begin
raise EOFException.create('ZIPArchiveReader: архив повреждён или имеет неправильный формат.');
end;
arraycopy(bytes, DESCRIPTOR_SIZE, bytes, 0, DESCRIPTOR_SIZE);
until false;
end;
constructor ZIPArchiveReader.create(source: Input);
begin
inherited create();
self.onEnd := true;
self.archive := DataInputStream.create(source);
if source <> nil then begin
self.start := source.position();
checkEnd();
end;
end;
procedure ZIPArchiveReader.gotoFirstFile();
var
archive: Input;
begin
archive := self.archive;
archive.seek(start - archive.position());
checkEnd();
end;
procedure ZIPArchiveReader.gotoNextFile();
var
flags: int;
compressedSize: int;
nameExtraLength: int;
archive: DataInput;
begin
if onEnd then begin
exit;
end;
archive := self.archive;
archive.seek($02);
flags := archive.readUnsignedShortLE();
archive.seek($0a);
compressedSize := archive.readIntLE();
archive.seek($04);
nameExtraLength := archive.readUnsignedShortLE() + archive.readUnsignedShortLE();
archive.seek(long(nameExtraLength));
if ((flags and $08) <> 0) and (compressedSize = 0) then begin
compressedSize := int(getFileSizes());
end;
archive.seek(zeroExtend(compressedSize));
checkEnd();
end;
function ZIPArchiveReader.isStayOnEnd(): boolean;
begin
result := onEnd;
end;
function ZIPArchiveReader.openFile(const fileName: AnsiString): Input;
begin
gotoFirstFile();
while (not onEnd) and (getCurrentFileName() <> fileName) do begin
gotoNextFile();
end;
if onEnd then begin
result := nil;
exit;
end;
result := openCurrentFile();
end;
function ZIPArchiveReader.openCurrentFile(): Input;
var
archive: DataInput;
flags: int;
compressionMethod: int;
compressedSize: int;
decompressedSize: int;
nameExtraLength: int;
fileSizes: long;
fileContents: byte_Array1d;
begin
if onEnd then begin
result := nil;
exit;
end;
archive := self.archive;
archive.seek($02);
flags := archive.readUnsignedShortLE();
compressionMethod := archive.readUnsignedShortLE();
archive.seek($08);
compressedSize := archive.readIntLE();
decompressedSize := archive.readIntLE();
nameExtraLength := archive.readUnsignedShortLE() + archive.readUnsignedShortLE();
archive.seek(nameExtraLength);
if ((flags and $08) <> 0) and (compressedSize = 0) then begin
fileSizes := getFileSizes();
compressedSize := int(fileSizes);
decompressedSize := int(fileSizes shr 32);
end;
if ((flags and $01) = 0) and ((compressionMethod = METHOD_NO_COMPRESSION) or (compressionMethod = METHOD_DEFLATED)) and (compressedSize >= 0) and (decompressedSize >= 0) then begin
setLength(fileContents, compressedSize);
archive.read(fileContents);
if compressionMethod = METHOD_DEFLATED then begin
fileContents := Zlib.decompress(fileContents, false);
end;
result := ByteArrayInputStream.create(fileContents, 0, length(fileContents));
end else begin
archive.seek(zeroExtend(compressedSize));
result := nil;
end;
checkEnd();
end;
function ZIPArchiveReader.getCurrentFileName(): AnsiString;
var
archive: DataInput;
len: int;
i: int;
fileName: AnsiString;
begin
if onEnd then begin
result := '';
exit;
end;
archive := self.archive;
archive.seek($16);
len := archive.readUnsignedShortLE();
archive.seek($02);
fileName := String_create(len);
for i := 1 to len do begin
fileName[i] := char(archive.read());
end;
archive.seek(long(-(len + $1a)));
result := ISO88591toUTF8(fileName);
end;
class function ZIPArchiveReader.ISO88591toUTF8(const fileName: AnsiString): AnsiString;
var
c: int;
i: int;
utf8Name: AnsiString;
begin
utf8Name := '';
for i := 1 to length(fileName) do begin
c := int(fileName[i]);
if (c >= $01) and (c <= $7f) then begin
utf8Name := utf8Name + (char(c));
end else begin
utf8Name := utf8Name + (char($c0 + (c shr $06)) + char($80 + (c and $3f)));
end;
end;
result := utf8Name;
end;
{%endregion}
end.