Subversion Repositories autosfx

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 daniel-mar 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