Subversion Repositories autosfx

Rev

Blame | Last modification | View Log | RSS feed

  1. Unit ZipFix;
  2.  
  3. //---------------------------------------------------------------------
  4. // Component:        TZipFix
  5. // Author:           Angus Johnson
  6. // Version:          1.1
  7. // Delphi:           Versions 3 - 5.
  8. // C++ Builder:      Versions 1, 3 and 4 (5 Untested).
  9. // Date:             30 May 2000
  10. // Copyright:        © 1999-2000 Angus Johnson
  11. // Email:            ajohnson@rpi.net.au
  12. // Distribution:     Freeware.
  13. //
  14. // Component to repair a Zip archive when the archive's
  15. // directory structure has been damaged.
  16. // It will *not* fix damaged zipped data nor will it
  17. // solve 'forgotten' passwords.
  18. // If some zipped data has been damaged, the remaining undamaged
  19. // data can be rebuilt into a new archive.
  20. // Multi-disk archives can also be repaired if the disks are first
  21. // concatenated (maintaining order) into a single InStream.
  22. // Can also extract a zip archive which has been embedded
  23. // in another file (eg a self-extracting zip archive).
  24. //
  25. // 21-06-2000 Added ZipFix.res RCV
  26. //---------------------------------------------------------------------
  27. (*
  28.  Copyright (C) 2009, 2010  by Russell J. Peters, Roger Aelbrecht,
  29.       Eric W. Engler and Chris Vleghert.
  30.  
  31.    This file is part of TZipMaster Version 1.9.
  32.  
  33.     TZipMaster is free software: you can redistribute it and/or modify
  34.     it under the terms of the GNU Lesser General Public License as published by
  35.     the Free Software Foundation, either version 3 of the License, or
  36.     (at your option) any later version.
  37.  
  38.     TZipMaster is distributed in the hope that it will be useful,
  39.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  40.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  41.     GNU Lesser General Public License for more details.
  42.  
  43.     You should have received a copy of the GNU Lesser General Public License
  44.     along with TZipMaster.  If not, see <http://www.gnu.org/licenses/>.
  45.  
  46.     contact: problems@delphizip.org (include ZipMaster in the subject).
  47.     updates: http://www.delphizip.org
  48.     DelphiZip maillist subscribe at http://www.freelists.org/list/delphizip
  49. *)
  50. // Acknowledgements for suggestions can be found within code.
  51.  
  52. {$IfDef VER110}
  53.    {$ObjExportAll On}
  54.    {$DEFINE CBUILDER3}
  55. {$EndIf}
  56. {$IfDef VER125}
  57.    {$ObjExportAll On}
  58.    {$DEFINE CBUILDER4}
  59. {$EndIf}
  60. {$IfDef VER130}
  61.  {$IfDef BCB}
  62.    {$ObjExportAll On}
  63.    {$DEFINE CBUILDER5}
  64.  {$EndIf}
  65. {$EndIf}
  66.  
  67. Interface
  68.  
  69. uses
  70.   Windows, Messages, SysUtils, Classes;
  71.  
  72. type
  73.  
  74.   TOnFileFoundEvent = procedure (Sender: TObject;
  75.     const Filename: string; FileInfoIsOK: boolean) of object;
  76.  
  77.   TZipFix = class(TComponent)
  78.   private
  79.     fBuffer: pchar;
  80.     fBufferSize: integer;
  81.     fFileList: TList;
  82.     fOutStream: TStream;
  83.     fOnFileFound: TOnFileFoundEvent;
  84.     fJumpValue: array[#0..#255] of integer; //used to find Local Header records
  85.     fJumpValue2: array[#0..#255] of integer; //used to find DataDescriptor records
  86.     procedure InitializeArrays;
  87.     procedure GetLocalHeaderInfo;
  88.     procedure DoZipFix;
  89.   public
  90.     procedure Execute(InStream: TMemoryStream; OutStream: TStream); {$IFDEF VER130} overload;
  91.     procedure Execute(const InFilename, OutFilename: string); overload; {$ENDIF}
  92.   published
  93.     //30 May 2000 - OnFileFound event moved to published
  94.     property OnFileFound: TOnFileFoundEvent read fOnFileFound write fOnFileFound;
  95.   end;
  96.  
  97.   procedure Register;
  98.  
  99. Implementation
  100.  
  101. {$R ZipFix.Res}
  102.  
  103. type
  104.  
  105. //---------------------------------------------------------------------
  106. //Record structures used in Zip files...
  107. //---------------------------------------------------------------------
  108.   TLocalHeader = packed record
  109.     HeaderSig          : Cardinal; // $04034b50 (4)
  110.     VersionNeed        : Word;
  111.     Flag               : Word;
  112.     ComprMethod        : Word;
  113.     FileTime           : Word;
  114.     FileDate           : Word;
  115.     CRC32              : Cardinal;
  116.     ComprSize          : Cardinal;
  117.     UnComprSize        : Cardinal;
  118.     FileNameLen        : Word;
  119.     ExtraLen           : Word;
  120.   end;
  121.  
  122.   TDataDescriptor = packed record  //Exists only if bit 3 of LocalHeader.Flag is set.
  123.     DescriptorSig      : Cardinal; //field not defined in PKWare's docs but used by WinZip
  124.     CRC32              : Cardinal;
  125.     ComprSize          : Cardinal;
  126.     UnComprSize        : Cardinal;
  127.   end;
  128.  
  129. (*
  130.   CentralDirectory =
  131.     array [1..N] of TCentralFileHeader;
  132.     TEndOfCentralHeader;
  133. *)
  134.  
  135.   //array of TCentralFileHeaders constitute the Central Header directory...
  136.   TCentralFileHeader = packed record  // fixed part size = 42 bytes
  137.     HeaderSig          : Cardinal; // $02014b50 { 'PK'#1#2 } (4)
  138.     MadeByVersion      : Byte;    //(1)
  139.     HostVersionNo      : Byte;    //(1)
  140.     Version            : Word;    //(2) version needed to extract(2)
  141.     Flag               : Word;    //(2)
  142.     CompressionMethod  : Word;    //(2)
  143.     FileDate           : Integer; //modification date & time (4)
  144.     CRC32              : Integer; //(4)
  145.     CompressedSize     : Integer; //(4)
  146.     UncompressedSize   : Integer; //(4)
  147.     FileNameLength     : Word;    //(2)
  148.     ExtraFieldLength   : Word;    //(2)
  149.     FileCommentLen     : Word;    //(2)
  150.     StartOnDisk        : Word;    //disk # on which file starts (2)
  151.     IntFileAttrib      : Word;    //internal file attributes ie: Text/Binary(2)
  152.     ExtFileAttrib      : Cardinal;//external file attributes(4)
  153.     RelOffLocalHdr     : Cardinal;//relative offset of local file header(4)
  154.     //FileName         variable size
  155.     //ExtraField       variable size
  156.     //FileComment      variable size
  157.   end;
  158.  
  159.   TEndOfCentralHeader = packed record  //Fixed part size = 22 bytes
  160.     HeaderSig          : Cardinal; //$06054B50 (4)
  161.     ThisDiskNo         : Word;     //This disk's number (zero based) (2)
  162.     CentralDiskNo      : Word;     //Disk number on which central dir starts (2)
  163.     ThisDiskEntries    : Word;     //Number of central dir entries on this disk (2)
  164.     TotalEntries       : Word;     //Total entries in central dir (2)
  165.     CentralSize        : Cardinal; //Size of central directory (4)
  166.     CentralOffset      : Cardinal; //offset of central dir on CentralDiskNo (4)
  167.     ZipCommentLen      : Word;     //(2)
  168.     // ZipComment       variable size
  169.   end;
  170.  
  171. //---------------------------------------------------------------------
  172. //Record structure used internally by the TZipFix
  173. //---------------------------------------------------------------------
  174.  
  175.   pFileInfo = ^TFileInfo;
  176.   TFileInfo = packed record //first 42 bytes identical to the Central Header File record
  177.     MadeByVersion      : Byte;    //(1)
  178.     HostVersionNo      : Byte;    //(1)
  179.     Version            : Word;    //(2)
  180.     Flag               : Word;    //(2)
  181.     CompressionMethod  : Word;    //(2)
  182.     FileDate           : Integer; //modification datetime (4)
  183.     CRC32              : Integer; //(4)
  184.     CompressedSize     : Integer; //(4)
  185.     UncompressedSize   : Integer; //(4)
  186.     FileNameLength     : Word;    //(2)
  187.     ExtraFieldLength   : Word;    //(2)
  188.     FileCommentLen     : Word;    //(2)
  189.     StartOnDisk        : Word;    //disk # on which file starts (2)
  190.     IntFileAttrib      : Word;    //internal file attributes ie: Text/Binary(2)
  191.     ExtFileAttrib      : Cardinal;//external file attributes(4)
  192.     RelOffLocalHdr     : Cardinal;//relative offset of local file header(4)
  193.     //42 bytes above plus...
  194.     Filename: string;
  195.     //ExtraField: string;
  196.     //Comment: string;
  197.   end;
  198.  
  199. const
  200.   MULTIPLE_DISK_SIG      = $08074b50; // 'PK'#7#8
  201.   DATA_DESCRIPT_SIG      = MULTIPLE_DISK_SIG; //!!
  202.   LOCAL_HEADERSIG        = $04034b50; // 'PK'#3#4
  203.   CENTRAL_HEADERSIG      = $02014b50; // 'PK'#1#2
  204.   EOC_HEADERSIG          = $06054b50; // 'PK'#5#6
  205.  
  206.   MAX_FILENAME_LEN       = 80;
  207.  
  208. procedure Register;
  209. begin
  210.   RegisterComponents('Samples', [TZipFix]);
  211. end;
  212.  
  213. //------------------------------------------------------------------------------
  214. // TZipFix Methods
  215. //------------------------------------------------------------------------------
  216.  
  217. //fill fFileList with records containing info from the Local Headers
  218. procedure TZipFix.GetLocalHeaderInfo;
  219. var
  220.   fileInfo: pFileInfo;
  221.   CurrentPos: integer;
  222.   i, HeaderStartPos: integer;
  223.   DataStart,DataEnd: integer;
  224. label
  225.   LocalHeaderError;
  226.   //-------------------------------
  227.  
  228.   //positions CurrentPos at start of LocalHeaderSig...
  229.   function FindNextHeader: boolean;
  230.   var
  231.     n,HeaderSig: integer;
  232.   begin
  233.     result := false;
  234.     while CurrentPos < fBufferSize do
  235.     begin
  236.       n := fJumpValue[fBuffer[CurrentPos]];
  237.       if n = 0 then //looking for 'PK'#3#4, a #4 found at least...
  238.       begin
  239.         dec(CurrentPos,3);
  240.         move(fBuffer[CurrentPos],HeaderSig,4);
  241.         if (HeaderSig = LOCAL_HEADERSIG) and
  242.           (CurrentPos + Sizeof(TLocalHeader) < fBufferSize) then
  243.         begin
  244.           result := true;
  245.           exit;
  246.         end
  247.         else
  248.           inc(CurrentPos,7);
  249.       end
  250.       else
  251.         inc(CurrentPos,n);
  252.     end;
  253.   end;
  254.   //-------------------------------
  255.  
  256.   //positions fCurrentPos at start of DataDescriptorSig...
  257.   function FindDataDescriptor: boolean;
  258.   var
  259.     n,HeaderSig: integer;
  260.   begin
  261.     result := false;
  262.     while CurrentPos < fBufferSize do
  263.     begin
  264.       n := fJumpValue2[fBuffer[CurrentPos]];
  265.       if n = 0 then //looking for 'PK'#7#8, a #8 found at least...
  266.       begin
  267.         dec(CurrentPos,3);
  268.         move(fBuffer[CurrentPos],HeaderSig,4);
  269.         if (HeaderSig = DATA_DESCRIPT_SIG) and
  270.           (CurrentPos + Sizeof(TDataDescriptor) < fBufferSize) then
  271.         begin
  272.           result := true;
  273.           exit;
  274.         end
  275.         else
  276.           inc(CurrentPos,7);
  277.       end
  278.       else
  279.         inc(CurrentPos,n);
  280.     end;
  281.   end;
  282. //-------------------------------
  283.  
  284. begin
  285.  
  286.   //prepare for boyer-moore-horspool searches...
  287.   //this will be more than 3 times faster than a brute-force search
  288.   if fJumpValue[#0] = 0 then InitializeArrays;
  289.  
  290.   CurrentPos := 3;
  291.  
  292.   //get all local header info...
  293.   while FindNextHeader do
  294.   begin
  295.     HeaderStartPos := CurrentPos;
  296.     new(fileInfo);
  297.     with fileInfo^ do
  298.     begin
  299.       //ignore the following values, so zero initialize them.
  300.       //we could try and match them to the dud central directory records
  301.       //but i'm not sure it's worth the trouble.
  302.       MadeByVersion := $0;
  303.       HostVersionNo := $0;
  304.       IntFileAttrib := $0;
  305.       ExtFileAttrib := $0;
  306.       StartOnDisk   := $0;
  307.       FileCommentLen := $0;
  308.  
  309.       //copy - Version, Flag, CompressionMethod, FileDate, CRC32,
  310.       //  CompressedSize, UncompressedSize, FileNameLength, ExtraFieldLength
  311.       move(fBuffer[HeaderStartPos+4],Version,Sizeof(TLocalHeader)-4);
  312.       //save current Local Header offset which will be updated later...
  313.       RelOffLocalHdr := HeaderStartPos;
  314.       if (fileInfo.FileNameLength < 1) or
  315.           (FileNameLength > MAX_FILENAME_LEN) then
  316.         goto LocalHeaderError;
  317.       inc(CurrentPos, Sizeof(TLocalHeader));
  318.       setlength(Filename,FileNameLength);
  319.       move(fBuffer[CurrentPos],Filename[1],FileNameLength);
  320.       //and do an extra check to make sure the name is valid...
  321.       for i := 1 to FileNameLength do
  322.         if Filename[i] < #32 then
  323.         begin
  324.           Filename := '';
  325.           goto LocalHeaderError;
  326.         end;
  327.       inc(CurrentPos, FileNameLength + ExtraFieldLength);
  328.       if (Flag and $8) = $8 then
  329.       begin
  330.         //a bit of a bummer but a TDataDescriptor record is used
  331.         //so we don't yet know the size of the data block.
  332.         //it's a little bit slower but it still works...
  333.         DataStart:= CurrentPos;
  334.         if not FindDataDescriptor then goto LocalHeaderError;
  335.         DataEnd:= CurrentPos;
  336.         //now update: CRC32, CompressedSize, UncompressedSize
  337.         move(fBuffer[CurrentPos+4],CRC32,12);
  338.         inc(CurrentPos, sizeof(TDataDescriptor)); //get ready for next LocalHeader
  339.         if (CompressedSize <> DataEnd - DataStart) then
  340.           goto LocalHeaderError;
  341.       end
  342.       else
  343.         inc(CurrentPos,CompressedSize);
  344.     end; //with fileInfo^
  345.     //check for corrupted CompressedSize - suggested by Ramon Speets
  346.     if (CurrentPos > fBufferSize) or
  347.        (fileInfo.CompressedSize > fileInfo.UncompressedSize) then
  348.       goto LocalHeaderError;
  349.     fFileList.add(fileInfo);
  350.     if assigned(fOnFileFound) then fOnFileFound(self,fileInfo.filename,true);
  351.     continue; //avoid LocalHeaderError below
  352.  
  353. LocalHeaderError:
  354.     if assigned(fOnFileFound) then fOnFileFound(self,fileInfo.filename,false);
  355.     dispose(fileInfo);
  356.     CurrentPos := HeaderStartPos + 4; //ie: skip over this dud record
  357.   end; //while FindNextHeader
  358. end;
  359. //------------------------------------------------------------------------------
  360.  
  361. procedure TZipFix.InitializeArrays;
  362. var
  363.   i: char;
  364. begin
  365.   for i := #0 to #255 do fJumpValue[i] := 4;
  366.   fJumpValue['P'] := 3;
  367.   fJumpValue['K'] := 2;
  368.   fJumpValue[#3] := 1;
  369.   fJumpValue[#4] := 0;
  370.  
  371.   for i := #0 to #255 do fJumpValue2[i] := 4;
  372.   fJumpValue2['P'] := 3;
  373.   fJumpValue2['K'] := 2;
  374.   fJumpValue2[#7] := 1;
  375.   fJumpValue2[#8] := 0;
  376. end;
  377. //---------------------------------------------------------------------
  378.  
  379. procedure TZipFix.DoZipFix;
  380. var
  381.   i,CurrentPos, StartOfCentral: integer;
  382.   Eoc: TEndOfCentralHeader;
  383.   HeaderSig: Cardinal;
  384. begin
  385.   //fOutStream.position := 0; //not essential...
  386.                               //could theoretically append an SFX stub.
  387.   GetLocalHeaderInfo;
  388.   if fFileList.count = 0 then exit; //no files can be restored :(
  389.  
  390.   //write all the local headers and data...
  391.   for i := 0 to fFileList.count-1 do
  392.     with pFileInfo(fFileList[i])^ do
  393.     begin
  394.       CurrentPos := RelOffLocalHdr;
  395.       RelOffLocalHdr := fOutStream.Position; //now update RelOffLocalHdr
  396.       fOutStream.write(fbuffer[CurrentPos],
  397.         sizeof(TLocalHeader)+FileNameLength+ExtraFieldLength+CompressedSize);
  398.       //i'm almost certain the Central Directory ExtraField is different
  399.       //from the local ExtraField so zero this out for the Central Directory.
  400.       ExtraFieldLength := 0;
  401.     end;
  402.   StartOfCentral := fOutStream.position;
  403.   //recreate the central directory...
  404.   HeaderSig := CENTRAL_HEADERSIG;
  405.   for i := 0 to fFileList.count-1 do
  406.     with pFileInfo(fFileList[i])^ do
  407.     begin
  408.       fOutStream.write(HeaderSig,sizeof(HeaderSig));
  409.       //copy first 42 bytes starting at MadeByVersion...
  410.       fOutStream.write(MadeByVersion,42);
  411.       fOutStream.write(Filename[1],length(Filename));
  412.     end;
  413.   //finally write the EndOfCentral header...
  414.   Eoc.HeaderSig := EOC_HEADERSIG;
  415.   Eoc.ThisDiskNo := 0;
  416.   Eoc.CentralDiskNo := 0;
  417.   Eoc.ThisDiskEntries := fFileList.count;
  418.   Eoc.TotalEntries := Eoc.ThisDiskEntries;
  419.   Eoc.CentralSize := fOutStream.position - StartOfCentral;
  420.   Eoc.CentralOffset := StartOfCentral;
  421.   Eoc.ZipCommentLen := 0;
  422.   fOutStream.write(Eoc,sizeof(Eoc));
  423. end;
  424. //---------------------------------------------------------------------
  425.  
  426. procedure TZipFix.Execute(InStream: TMemoryStream; OutStream: TStream);
  427. var
  428.   i: integer;
  429. begin
  430.   if (InStream = nil) or (OutStream = nil) then
  431.     raise Exception.create('No input or no output stream has been defined');
  432.   fBuffer := InStream.memory;
  433.   fBufferSize := InStream.size;
  434.   fOutStream := OutStream;
  435.  
  436.   fFileList:= TList.create;
  437.   try
  438.     DoZipFix; //do it here!!
  439.   finally
  440.     //cleanup...
  441.     for i := 0 to fFileList.count -1 do
  442.       dispose(pFileInfo(fFileList[i]));
  443.     fFileList.free;
  444.   end;
  445. end;
  446. //------------------------------------------------------------------------------
  447.  
  448. {$IFDEF VER130}
  449. procedure TZipFix.Execute(const InFilename, OutFilename: string);
  450. var
  451.   InStream: TMemoryStream;
  452.   OutStream: TFileStream;
  453. begin
  454.   OutStream:= TFileStream.create(OutFilename,fmCreate);
  455.   InStream := TMemoryStream.create;
  456.   try
  457.     Instream.LoadFromFile(InFilename);
  458.     Execute(InStream, OutStream);
  459.   finally
  460.     InStream.free;
  461.     OutStream.free;
  462.   end;
  463. end;
  464.  
  465. //------------------------------------------------------------------------------
  466. {$ENDIF}
  467. End.
  468.  
  469.