#1 2015-10-30 09:33:05

P.Herlant
Member
Registered: 2015-10-30
Posts: 1

TZipRead enhancement to cope with Self-Extracting zip files

Hello Arnaud,

I'm using TZipRead (in SynZip.pas) but I have a file it couldn't read, whereas every other zip reader I've tried managed to unzip it without problem.

It appears that this file (a printer drivers package for a bar code label printing software, you can download it at "http://ftp.nicelabel.com/software/drivers/sartorius.exe") has 2 features that your code didn't support :
1) it's a self-extracting zip file
2) it's a digitally signed file

The thing is, it's a perfectly valid zip file :
- the extraction program is at the beginning of the file and the file addresses in the zip central directory take this offset into account
- the digital signature is at the end of the file, after the EOCD zip header, so it doesn't interfere with zip reading code

So I've done a couple of slight modifications into SynZip.pas that work great here.
Could you review/optimize them and if it suits you, include it in the Synopse's source ? Unless there is something I didn't have seen...
Thank you !

Note: I had to modify TZipRead.Create(aFile: THandle; ZipStartOffset: cardinal=0; Size: cardinal=0).
But I didn't quite understood the use case for the look-ups inside (ExeOffset part), so I removed them, leaving the checks to the main Create function.
Indeed, I understood that it was for a case where we provide a particularly crafted file with a raw concatenation of a zip stream inside another file.
So the zip files addresses in the zip central directory are not relative to the beginning of the file but to a particular point in the file.
In this case, I think that's the job of the function caller to provide the suitable ZipStartOffset/Size to find the zip file data inside the container file, no ?

constructor TZipRead.Create(BufZip: pByteArray; Size: cardinal);
var lhr: PLastHeader;
    H: PFileHeader;
    lfhr: PLocalFileHeader;
    i,j: integer;
    {$ifdef CONDITIONALEXPRESSIONS}
    tmp: UTF8String;
    {$else}
    tmp: ZipString;
    {$endif}
begin
  // resources size may be rounded up to alignment
  // or data can come from a signed file (with signature at the end)
  lhr:=nil;
  for i := Size-sizeof(TLastHeader) downto 0 do begin
    lhr := @BufZip[i];
    // +1 avoids false positive
    if lhr^.signature+1=LASTHEADER_SIGNATURE_INC then
      break;
  end;
  if (lhr=nil) or (lhr^.headerOffset>=Size) then begin
    UnMap;
    raise ESynZipException.Create('ZIP format');
  end;
  SetLength(Entry,lhr^.totalFiles); // fill Entry[] with the Zip headers
  ReadOffset := lhr^.headerOffset;
  FirstFileHeader := @BufZip[lhr^.headerOffset];
  H := FirstFileHeader;
  for i := 1 to lhr^.totalFiles do begin
    if H^.signature+1<>ENTRY_SIGNATURE_INC then begin // +1 to avoid match in exe
      UnMap;
      raise ESynZipException.Create('ZIP format');
    end;
    lfhr := @BufZip[H^.localHeadOff];
    with lfhr^.fileInfo do
    if flags and (1 shl 3)<>0 then begin // crc+sizes in "data descriptor"
      if (zcrc32<>0) or (zzipSize<>0) or (zfullSize<>0) then
        raise ESynZipException.Create('ZIP extended format');
      // UnZip() will call RetrieveFileInfo()
    end else
      if (zzipSize=cardinal(-1)) or (zfullSize=cardinal(-1)) then
        raise ESynZipException.Create('ZIP64 format not supported');
    with Entry[Count] do begin
      infoLocal := @lfhr^.fileInfo;
      infoDirectory := H;
      storedName := PAnsiChar(lfhr)+sizeof(lfhr^);
      data := storedName+infoLocal^.NameLen+infoLocal^.extraLen; // data mapped in memory
      SetString(tmp,storedName,infoLocal^.nameLen);
      for j := 0 to infoLocal^.nameLen-1 do
        if storedName[j]='/' then // normalize path delimiter
          PAnsiChar(Pointer(tmp))[j] := '\';
      {$ifdef CONDITIONALEXPRESSIONS}
      // Delphi 5 doesn't have UTF8Decode/UTF8Encode functions -> make 7 bit version
      if infoLocal^.GetUTF8FileName then
        // decode UTF-8 file name into native string/TFileName type
        zipName := UTF8Decode(tmp) else
      {$endif}
      begin
        // decode OEM/DOS file name into native string/TFileName type
        SetLength(zipName,infoLocal^.nameLen);
        OemToChar(Pointer(tmp),Pointer(zipName)); // OemToCharW/OemToCharA
      end;
      inc(PByte(H),sizeof(H^)+infoLocal^.NameLen+H^.fileInfo.extraLen+H^.commentLen);
      if not(infoLocal^.zZipMethod in [Z_STORED,Z_DEFLATED]) then
        raise ESynZipException.CreateFmt(
          'Unsupported compression method %d for %s',[infoLocal^.zZipMethod,zipName]);
      if (zipName='') or (zipName[length(zipName)]='\') then
        continue; // ignore folder
      if infoLocal^.flags and (1 shl 3)=0 then
        if (infoLocal^.zzipSize=0) or (infoLocal^.zfullSize=0) then
          raise ESynZipException.CreateFmt('"%s" size=0 in ZIP',[zipName]);
      inc(Count); // add file to Entry[]
    end;
  end;
  SetLength(Entry,Count); // remove unused entries (corresponding to ignored folders)
end;

constructor TZipRead.Create(aFile: THandle; ZipStartOffset: cardinal=0;
      Size: cardinal=0);
begin
  if aFile<=0 then
    exit;
  if Size=0 then
    Size := GetFileSize(aFile, nil)-ZipStartOffset;
  map := CreateFileMapping(aFile, nil, PAGE_READONLY, 0, 0, nil);
  if map=0 then begin
    Unmap;
    raise ESynZipException.Create('Missing File');
  end;
  Buf := MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
  Create(@Buf[ZipStartOffset], Size);
end;

Offline

Board footer

Powered by FluxBB