:pserver:cvsanon@mok.lvcm.com:/CVS/ReactOS reactos
[reactos.git] / apps / utils / cabman / cabinet.cpp
1 /*
2  * COPYRIGHT:   See COPYING in the top level directory
3  * PROJECT:     ReactOS cabinet manager
4  * FILE:        apps/cabman/cabinet.cpp
5  * PURPOSE:     Cabinet routines
6  * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7  * NOTES:       Define CAB_READ_ONLY for read only version
8  * REVISIONS:
9  *   CSH 21/03-2001 Created
10  * TODO:
11  *   - Checksum of datablocks should be calculated
12  *   - EXTRACT.EXE complains if a disk is created manually
13  *   - Folders that are created manually and span disks will result in a damaged cabinet
14  */
15 #include <windows.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include "cabinet.h"
19 #include "raw.h"
20 #include "mszip.h"
21
22
23 #ifndef CAB_READ_ONLY
24
25 #ifdef DBG
26
27 VOID DumpBuffer(PVOID Buffer, DWORD Size)
28 {
29     HANDLE FileHandle;
30     DWORD BytesWritten;
31
32     /* Create file, overwrite if it already exists */
33     FileHandle = CreateFile("dump.bin", // Create this file
34         GENERIC_WRITE,                  // Open for writing
35         0,                              // No sharing
36         NULL,                           // No security
37         CREATE_ALWAYS,                  // Create or overwrite
38         FILE_ATTRIBUTE_NORMAL,          // Normal file 
39         NULL);                          // No attribute template
40     if (FileHandle == INVALID_HANDLE_VALUE) {
41         DPRINT(MID_TRACE, ("ERROR OPENING '%d'.\n", (UINT)GetLastError()));
42         return;
43     }
44
45     if (!WriteFile(FileHandle, Buffer, Size, &BytesWritten, NULL)) {
46         DPRINT(MID_TRACE, ("ERROR WRITING '%d'.\n", (UINT)GetLastError()));
47     }
48
49     CloseHandle(FileHandle);
50 }
51
52 #endif /* DBG */
53
54
55 /* CCFDATAStorage */
56
57 CCFDATAStorage::CCFDATAStorage()
58 /*
59  * FUNCTION: Default constructor
60  */
61 {
62     FileCreated = FALSE;
63 }
64
65
66 CCFDATAStorage::~CCFDATAStorage()
67 /*
68  * FUNCTION: Default destructor
69  */
70 {
71     ASSERT(!FileCreated);
72 }
73
74
75 ULONG CCFDATAStorage::Create(LPTSTR FileName)
76 /*
77  * FUNCTION: Creates the file
78  * ARGUMENTS:
79  *     FileName = Pointer to name of file
80  * RETURNS:
81  *     Status of operation
82  */
83 {
84     TCHAR FullName[MAX_PATH];
85
86     ASSERT(!FileCreated);
87
88     if (GetTempPath(MAX_PATH, FullName) == 0)
89         return CAB_STATUS_CANNOT_CREATE;
90
91     lstrcat(FullName, FileName);
92
93     /* Create file, overwrite if it already exists */
94     FileHandle = CreateFile(FullName,   // Create this file
95         GENERIC_READ | GENERIC_WRITE,   // Open for reading/writing
96         0,                              // No sharing
97         NULL,                           // No security
98         CREATE_ALWAYS,                  // Create or overwrite
99         FILE_FLAG_SEQUENTIAL_SCAN |     // Optimize for sequential scans
100         FILE_FLAG_DELETE_ON_CLOSE |     // Delete file when closed
101         FILE_ATTRIBUTE_TEMPORARY,       // Temporary file
102         NULL);                          // No attribute template
103     if (FileHandle == INVALID_HANDLE_VALUE) {
104         DPRINT(MID_TRACE, ("ERROR '%d'.\n", (UINT)GetLastError()));
105         return CAB_STATUS_CANNOT_CREATE;
106     }
107
108     FileCreated = TRUE;
109
110     return CAB_STATUS_SUCCESS;
111 }
112
113
114 ULONG CCFDATAStorage::Destroy()
115 /*
116  * FUNCTION: Destroys the file
117  * RETURNS:
118  *     Status of operation
119  */
120 {
121     ASSERT(FileCreated);
122
123     CloseHandle(FileHandle);
124
125     FileCreated = FALSE;
126
127     return CAB_STATUS_SUCCESS;
128 }
129
130
131 ULONG CCFDATAStorage::Truncate()
132 /*
133  * FUNCTION: Truncate the scratch file to zero bytes
134  * RETURNS:
135  *     Status of operation
136  */
137 {
138     if (!SetFilePointer(FileHandle, 0, NULL, FILE_BEGIN))
139         return CAB_STATUS_FAILURE;
140     if (!SetEndOfFile(FileHandle))
141         return CAB_STATUS_FAILURE;
142     return CAB_STATUS_SUCCESS;
143 }
144
145
146 ULONG CCFDATAStorage::Position()
147 /*
148  * FUNCTION: Returns current position in file
149  * RETURNS:
150  *     Current position
151  */
152 {
153     return SetFilePointer(FileHandle, 0, NULL, FILE_CURRENT);
154 }
155
156
157 ULONG CCFDATAStorage::Seek(LONG Position)
158 /*
159  * FUNCTION: Seeks to an absolute position
160  * ARGUMENTS:
161  *     Position = Absolute position to seek to
162  * RETURNS:
163  *     Status of operation
164  */
165 {
166     if (SetFilePointer(FileHandle,
167         Position,
168         NULL,
169         FILE_BEGIN) == 0xFFFFFFFF)
170         return CAB_STATUS_FAILURE;
171     else
172         return CAB_STATUS_SUCCESS;
173 }
174
175
176 ULONG CCFDATAStorage::ReadBlock(PCFDATA Data, PVOID Buffer, PDWORD BytesRead)
177 /*
178  * FUNCTION: Reads a CFDATA block from the file
179  * ARGUMENTS:
180  *     Data         = Pointer to CFDATA block for the buffer
181  *     Buffer       = Pointer to buffer to store data read
182  *     BytesWritten = Pointer to buffer to write number of bytes read
183  * RETURNS:
184  *     Status of operation
185  */
186 {
187     if (!ReadFile(FileHandle, Buffer, Data->CompSize, BytesRead, NULL))
188         return CAB_STATUS_CANNOT_READ;
189
190     return CAB_STATUS_SUCCESS;
191 }
192
193
194 ULONG CCFDATAStorage::WriteBlock(PCFDATA Data, PVOID Buffer, PDWORD BytesWritten)
195 /*
196  * FUNCTION: Writes a CFDATA block to the file
197  * ARGUMENTS:
198  *     Data         = Pointer to CFDATA block for the buffer
199  *     Buffer       = Pointer to buffer with data to write
200  *     BytesWritten = Pointer to buffer to write number of bytes written
201  * RETURNS:
202  *     Status of operation
203  */
204 {
205     if (!WriteFile(FileHandle, Buffer, Data->CompSize, BytesWritten, NULL))
206         return CAB_STATUS_CANNOT_WRITE;
207
208     return CAB_STATUS_SUCCESS;
209 }
210
211 #endif /* CAB_READ_ONLY */
212
213
214 /* CCabinet */
215
216 CCabinet::CCabinet()
217 /*
218  * FUNCTION: Default constructor
219  */
220 {
221     FileOpen = FALSE;
222     lstrcpy(DestPath, "");
223
224     FolderListHead = NULL;
225     FolderListTail = NULL;
226     FileListHead   = NULL;
227     FileListTail   = NULL;
228
229     Codec         = new CRawCodec();
230     CodecId       = CAB_CODEC_RAW;
231     CodecSelected = TRUE;
232
233     OutputBuffer = NULL;
234     InputBuffer  = NULL;
235     MaxDiskSize  = 0;
236     BlockIsSplit = FALSE;
237     ScratchFile  = NULL;
238
239     FolderUncompSize = 0;
240     BytesLeftInBlock = 0;
241     ReuseBlock       = FALSE;
242     CurrentDataNode  = NULL;
243 }
244
245
246 CCabinet::~CCabinet()
247 /*
248  * FUNCTION: Default destructor
249  */
250 {
251     if (CodecSelected)
252         delete Codec;
253 }
254
255
256 LPTSTR CCabinet::GetFileName(LPTSTR Path)
257 /*
258  * FUNCTION: Returns a pointer to file name
259  * ARGUMENTS:
260  *     Path = Pointer to string with pathname
261  * RETURNS:
262  *     Pointer to filename
263  */
264 {
265     DWORD i, j;
266
267     j = i = (Path[0] ? (Path[1] == ':' ? 2 : 0) : 0);
268
269     while (Path [i++]) {
270         if (Path [i - 1] == '\\') j = i;
271     }
272     return Path + j;
273 }
274
275
276 VOID CCabinet::RemoveFileName(LPTSTR Path)
277 /*
278  * FUNCTION: Removes a file name from a path
279  * ARGUMENTS:
280  *     Path = Pointer to string with path
281  */
282 {
283     LPTSTR FileName;
284     DWORD i;
285
286     i = (Path [0] ? (Path[1] == ':' ? 2 : 0) : 0);
287     FileName = GetFileName(Path + i);
288
289     if ((FileName != (Path + i)) && (FileName [-1] == '\\'))
290         FileName--;
291     if ((FileName == (Path + i)) && (FileName [0] == '\\'))
292         FileName++;
293     FileName[0] = 0;
294 }
295
296
297 BOOL CCabinet::NormalizePath(LPTSTR Path,
298                              DWORD Length)
299 /*
300  * FUNCTION: Normalizes a path
301  * ARGUMENTS:
302  *     Path   = Pointer to string with pathname
303  *     Length = Number of bytes in Path
304  * RETURNS:
305  *     TRUE if there was enough room in Path, or FALSE
306  */
307 {
308     DWORD n;
309     BOOL OK = TRUE;
310
311     if ((n = lstrlen(Path)) &&
312         (Path[n - 1] != '\\') &&
313         (OK = ((n + 1) < Length))) {
314         Path[n]     = '\\';
315         Path[n + 1] = 0;
316     }
317     return OK;
318 }
319
320
321 LPTSTR CCabinet::GetCabinetName()
322 /*
323  * FUNCTION: Returns pointer to cabinet file name
324  * RETURNS:
325  *     Pointer to string with name of cabinet
326  */
327 {
328     return CabinetName;
329 }
330
331
332 VOID CCabinet::SetCabinetName(LPTSTR FileName)
333 /*
334  * FUNCTION: Sets cabinet file name
335  * ARGUMENTS:
336  *     FileName = Pointer to string with name of cabinet
337  */
338 {
339     lstrcpy(CabinetName, FileName);
340 }
341
342
343 VOID CCabinet::SetDestinationPath(LPTSTR DestinationPath)
344 /*
345  * FUNCTION: Sets destination path
346  * ARGUMENTS:
347  *    DestinationPath = Pointer to string with name of destination path
348  */
349 {
350     lstrcpy(DestPath, DestinationPath);
351     if (lstrlen(DestPath) > 0)
352         NormalizePath(DestPath, MAX_PATH);
353 }
354
355
356 LPTSTR CCabinet::GetDestinationPath()
357 /*
358  * FUNCTION: Returns destination path
359  * RETURNS:
360  *    Pointer to string with name of destination path
361  */
362 {
363     return DestPath;
364 }
365
366
367 DWORD CCabinet::GetCurrentDiskNumber()
368 /*
369  * FUNCTION: Returns current disk number
370  * RETURNS:
371  *     Current disk number
372  */
373 {
374     return CurrentDiskNumber;
375 }
376
377
378 ULONG CCabinet::Open()
379 /*
380  * FUNCTION: Opens a cabinet file
381  * RETURNS:
382  *     Status of operation
383  */
384 {
385     PCFFOLDER_NODE FolderNode;
386     ULONG Status;
387     DWORD Index;
388
389     if (!FileOpen) {
390         DWORD BytesRead;
391         DWORD Size;
392
393         OutputBuffer = HeapAlloc(GetProcessHeap(),
394             0, CAB_BLOCKSIZE + 12);    // This should be enough
395         if (!OutputBuffer)
396             return CAB_STATUS_NOMEMORY;
397
398         FileHandle = CreateFile(CabinetName, // Open this file
399             GENERIC_READ,                    // Open for reading 
400             FILE_SHARE_READ,                 // Share for reading 
401             NULL,                            // No security 
402             OPEN_EXISTING,                   // Existing file only 
403             FILE_ATTRIBUTE_NORMAL,           // Normal file 
404             NULL);                           // No attribute template 
405  
406         if (FileHandle == INVALID_HANDLE_VALUE) {
407             DPRINT(MID_TRACE, ("Cannot open file.\n"));
408             return CAB_STATUS_CANNOT_OPEN;
409         }
410
411         FileOpen = TRUE;
412
413         /* Load CAB header */
414         if ((Status = ReadBlock(&CABHeader, sizeof(CFHEADER), &BytesRead))
415             != CAB_STATUS_SUCCESS) {
416             DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
417             return CAB_STATUS_INVALID_CAB;
418         }
419
420         /* Check header */
421         if ((BytesRead                 != sizeof(CFHEADER)) ||
422             (CABHeader.Signature       != CAB_SIGNATURE   ) ||
423             (CABHeader.Version         != CAB_VERSION     ) ||
424             (CABHeader.FolderCount     == 0               ) ||
425             (CABHeader.FileCount       == 0               ) ||
426             (CABHeader.FileTableOffset < sizeof(CFHEADER))) {
427             CloseCabinet();
428             DPRINT(MID_TRACE, ("File has invalid header.\n"));
429             return CAB_STATUS_INVALID_CAB;
430         }
431
432         Size = 0;
433
434         /* Read/skip any reserved bytes */
435         if (CABHeader.Flags & CAB_FLAG_RESERVE) {
436             if ((Status = ReadBlock(&Size, sizeof(DWORD), &BytesRead))
437                 != CAB_STATUS_SUCCESS) {
438                 DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
439                 return CAB_STATUS_INVALID_CAB;
440             }
441             CabinetReserved = Size & 0xFFFF;
442             FolderReserved  = (Size >> 16) & 0xFF;
443             DataReserved    = (Size >> 24) & 0xFF;
444
445             SetFilePointer(FileHandle, CabinetReserved, NULL, FILE_CURRENT);
446             if (GetLastError() != NO_ERROR) {
447                 DPRINT(MIN_TRACE, ("SetFilePointer() failed.\n"));
448                 return CAB_STATUS_NOMEMORY;
449             }
450         }
451
452         if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0) {
453             /* Read name of previous cabinet */
454             Status = ReadString(CabinetPrev, 256);
455             if (Status != CAB_STATUS_SUCCESS)
456                 return Status;
457             /* Read label of previous disk */
458             Status = ReadString(DiskPrev, 256);
459             if (Status != CAB_STATUS_SUCCESS)
460                 return Status;
461         } else {
462             lstrcpy(CabinetPrev, "");
463             lstrcpy(DiskPrev,    "");
464         }
465   
466         if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0) {
467             /* Read name of next cabinet */
468             Status = ReadString(CabinetNext, 256);
469             if (Status != CAB_STATUS_SUCCESS)
470                 return Status;
471             /* Read label of next disk */
472             Status = ReadString(DiskNext, 256);
473             if (Status != CAB_STATUS_SUCCESS)
474                 return Status;
475         } else {
476             lstrcpy(CabinetNext, "");
477             lstrcpy(DiskNext,    "");
478         }
479
480         /* Read all folders */
481         for (Index = 0; Index < CABHeader.FolderCount; Index++) {
482             FolderNode = NewFolderNode();
483             if (!FolderNode) {
484                 DPRINT(MIN_TRACE, ("Insufficient resources.\n"));
485                 return CAB_STATUS_NOMEMORY;
486             }
487
488             if (Index == 0)
489                 FolderNode->UncompOffset = FolderUncompSize;
490
491             FolderNode->Index = Index;
492
493             if ((Status = ReadBlock(&FolderNode->Folder,
494                 sizeof(CFFOLDER), &BytesRead)) != CAB_STATUS_SUCCESS) {
495                 DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
496                 return CAB_STATUS_INVALID_CAB;
497             }
498         }
499
500         /* Read file entries */
501         Status = ReadFileTable();
502         if (Status != CAB_STATUS_SUCCESS) {
503             DPRINT(MIN_TRACE, ("ReadFileTable() failed (%d).\n", (UINT)Status));
504             return Status;
505         }
506
507         /* Read data blocks for all folders */
508         FolderNode = FolderListHead;
509         while (FolderNode != NULL) {
510             Status = ReadDataBlocks(FolderNode);
511             if (Status != CAB_STATUS_SUCCESS) {
512                 DPRINT(MIN_TRACE, ("ReadDataBlocks() failed (%d).\n", (UINT)Status));
513                 return Status;
514             }
515             FolderNode = FolderNode->Next;
516         }
517     }
518     return CAB_STATUS_SUCCESS;
519 }
520
521
522 VOID CCabinet::Close()
523 /*
524  * FUNCTION: Closes the cabinet file
525  */
526 {
527     if (FileOpen) {
528         CloseHandle(FileHandle);
529         FileOpen = FALSE;
530     }
531 }
532
533
534 ULONG CCabinet::FindFirst(LPTSTR FileName,
535                           PCAB_SEARCH Search)
536 /*
537  * FUNCTION: Finds the first file in the cabinet that matches a search criteria
538  * ARGUMENTS:
539  *     FileName = Pointer to search criteria
540  *     Search   = Pointer to search structure
541  * RETURNS:
542  *     Status of operation
543  */
544 {
545     RestartSearch = FALSE;
546     strncpy(Search->Search, FileName, MAX_PATH);
547     Search->Next = FileListHead;
548     return FindNext(Search);
549 }
550
551
552 ULONG CCabinet::FindNext(PCAB_SEARCH Search)
553 /*
554  * FUNCTION: Finds next file in the cabinet that matches a search criteria
555  * ARGUMENTS:
556  *     Search = Pointer to search structure
557  * RETURNS:
558  *     Status of operation
559  */
560 {
561     ULONG Status;
562
563     if (RestartSearch) {
564         Search->Next  = FileListHead;
565
566         /* Skip split files already extracted */
567         while ((Search->Next) &&
568             (Search->Next->File.FileControlID > CAB_FILE_MAX_FOLDER) &&
569             (Search->Next->File.FileOffset <= LastFileOffset)) {
570             DPRINT(MAX_TRACE, ("Skipping file (%s)  FileOffset (0x%X)  LastFileOffset (0x%X).\n",
571                 Search->Next->FileName, Search->Next->File.FileOffset, LastFileOffset));
572             Search->Next = Search->Next->Next;
573         }
574
575         RestartSearch = FALSE;
576     }
577
578     /* FIXME: Check search criteria */
579
580     if (!Search->Next) {
581         if (lstrlen(DiskNext) > 0) {
582             CloseCabinet();
583
584             SetCabinetName(CabinetNext);
585
586             OnDiskChange(CabinetNext, DiskNext);
587
588             Status = Open();
589             if (Status != CAB_STATUS_SUCCESS) 
590                 return Status;
591
592             Search->Next = FileListHead;
593             if (!Search->Next)
594                 return CAB_STATUS_NOFILE;
595         } else
596             return CAB_STATUS_NOFILE;
597     }
598
599     Search->File     = &Search->Next->File;
600     Search->FileName = Search->Next->FileName;
601     Search->Next     = Search->Next->Next;
602     return CAB_STATUS_SUCCESS;
603 }
604
605
606 ULONG CCabinet::ExtractFile(LPTSTR FileName)
607 /*
608  * FUNCTION: Extracts a file from the cabinet
609  * ARGUMENTS:
610  *     FileName = Pointer to buffer with name of file
611  * RETURNS
612  *     Status of operation
613  */
614 {
615     DWORD Size;
616     DWORD Offset;
617     DWORD BytesRead;
618     DWORD BytesToRead;
619     DWORD BytesWritten;
620     DWORD BytesSkipped;
621     DWORD BytesToWrite;
622     DWORD TotalBytesRead;
623     DWORD CurrentOffset;
624     PUCHAR Buffer;
625     PUCHAR CurrentBuffer;
626     HANDLE DestFile;
627     PCFFILE_NODE File;
628     CFDATA CFData;
629     ULONG Status;
630     BOOL Skip;
631     FILETIME FileTime;
632     TCHAR DestName[MAX_PATH];
633     TCHAR TempName[MAX_PATH];
634
635     Status = LocateFile(FileName, &File);
636     if (Status != CAB_STATUS_SUCCESS) {
637         DPRINT(MID_TRACE, ("Cannot locate file (%d).\n", (UINT)Status));
638         return Status;
639     }
640
641     LastFileOffset = File->File.FileOffset;
642
643     switch (CurrentFolderNode->Folder.CompressionType & CAB_COMP_MASK) {
644     case CAB_COMP_NONE:
645         SelectCodec(CAB_CODEC_RAW);
646         break;
647     case CAB_COMP_MSZIP:
648         SelectCodec(CAB_CODEC_MSZIP);
649         break;
650     default:
651         return CAB_STATUS_UNSUPPCOMP;
652     }
653
654     DPRINT(MAX_TRACE, ("Extracting file at uncompressed offset (0x%X)  Size (%d bytes)  AO (0x%X)  UO (0x%X).\n",
655         (UINT)File->File.FileOffset,
656         (UINT)File->File.FileSize,
657         (UINT)File->DataBlock->AbsoluteOffset,
658         (UINT)File->DataBlock->UncompOffset));
659
660     lstrcpy(DestName, DestPath);
661     lstrcat(DestName, FileName);
662
663     /* Create destination file, fail if it already exists */
664     DestFile = CreateFile(DestName,      // Create this file
665         GENERIC_WRITE,                   // Open for writing
666         0,                               // No sharing
667         NULL,                            // No security
668         CREATE_NEW,                      // New file only
669         FILE_ATTRIBUTE_NORMAL,           // Normal file
670         NULL);                           // No attribute template
671     if (DestFile == INVALID_HANDLE_VALUE) {
672         /* If file exists, ask to overwrite file */
673         if (((Status = GetLastError()) == ERROR_FILE_EXISTS) &&
674             (OnOverwrite(&File->File, FileName))) {
675             /* Create destination file, overwrite if it already exists */
676             DestFile = CreateFile(DestName, // Create this file
677                 GENERIC_WRITE,              // Open for writing
678                 0,                          // No sharing
679                 NULL,                       // No security
680                 TRUNCATE_EXISTING,          // Truncate the file
681                 FILE_ATTRIBUTE_NORMAL,      // Normal file
682                 NULL);                      // No attribute template
683             if (DestFile == INVALID_HANDLE_VALUE)
684                 return CAB_STATUS_CANNOT_CREATE;
685         } else {
686             if (Status == ERROR_FILE_EXISTS)
687                 return CAB_STATUS_FILE_EXISTS;
688             else
689                 return CAB_STATUS_CANNOT_CREATE;
690         }
691     }
692
693     if (!DosDateTimeToFileTime(File->File.FileDate, File->File.FileTime, &FileTime)) {
694         CloseHandle(DestFile);
695         DPRINT(MIN_TRACE, ("DosDateTimeToFileTime() failed (%d).\n", GetLastError()));
696         return CAB_STATUS_CANNOT_WRITE;
697     }
698
699     SetFileTime(DestFile, NULL, &FileTime, NULL);
700
701     SetAttributesOnFile(File);
702
703     Buffer = (PUCHAR)HeapAlloc(GetProcessHeap(),
704         0, CAB_BLOCKSIZE + 12); // This should be enough
705     if (!Buffer) {
706         CloseHandle(DestFile);
707         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
708         return CAB_STATUS_NOMEMORY; 
709     }
710
711     /* Call OnExtract event handler */
712     OnExtract(&File->File, FileName);
713
714         /* Search to start of file */
715         Offset = SetFilePointer(FileHandle,
716                 File->DataBlock->AbsoluteOffset,
717                 NULL,
718                 FILE_BEGIN);
719         if (GetLastError() != NO_ERROR) {
720                 DPRINT(MIN_TRACE, ("SetFilePointer() failed.\n"));
721                 return CAB_STATUS_INVALID_CAB;
722         }
723
724     Size   = File->File.FileSize;
725     Offset = File->File.FileOffset;
726     CurrentOffset = File->DataBlock->UncompOffset;
727
728     Skip = TRUE;
729
730         ReuseBlock = (CurrentDataNode == File->DataBlock);
731     if (Size > 0) {
732                 do {
733                         DPRINT(MAX_TRACE, ("CO (0x%X)    ReuseBlock (%d)    Offset (0x%X)   Size (%d)  BytesLeftInBlock (%d)\n",
734                                 File->DataBlock->UncompOffset, (UINT)ReuseBlock, Offset, Size,
735                                 BytesLeftInBlock));
736
737                         if (/*(CurrentDataNode != File->DataBlock) &&*/ (!ReuseBlock) || (BytesLeftInBlock <= 0)) {
738
739                                 DPRINT(MAX_TRACE, ("Filling buffer. ReuseBlock (%d)\n", (UINT)ReuseBlock));
740
741                 CurrentBuffer  = Buffer;
742                 TotalBytesRead = 0;
743                 do {
744                     DPRINT(MAX_TRACE, ("Size (%d bytes).\n", Size));
745
746                     if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) != 
747                         CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA))) {
748                         CloseHandle(DestFile);
749                         HeapFree(GetProcessHeap(), 0, Buffer);
750                         DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
751                         return CAB_STATUS_INVALID_CAB;
752                     }
753
754                     DPRINT(MAX_TRACE, ("Data block: Checksum (0x%X)  CompSize (%d bytes)  UncompSize (%d bytes)  Offset (0x%X).\n",
755                         (UINT)CFData.Checksum,
756                         (UINT)CFData.CompSize,
757                         (UINT)CFData.UncompSize,
758                                                 (UINT)SetFilePointer(FileHandle, 0, NULL, FILE_CURRENT)));
759
760                     ASSERT(CFData.CompSize <= CAB_BLOCKSIZE + 12);
761
762                     BytesToRead = CFData.CompSize;
763
764                                         DPRINT(MAX_TRACE, ("Read: (0x%X,0x%X).\n",
765                                                 CurrentBuffer, Buffer));
766
767                     if (((Status = ReadBlock(CurrentBuffer, BytesToRead, &BytesRead)) != 
768                         CAB_STATUS_SUCCESS) || (BytesToRead != BytesRead)) {
769                         CloseHandle(DestFile);
770                         HeapFree(GetProcessHeap(), 0, Buffer);
771                         DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
772                         return CAB_STATUS_INVALID_CAB;
773                     }
774
775                     /* FIXME: Does not work with files generated by makecab.exe */
776 /*
777                     if (CFData.Checksum != 0) {
778                         DWORD Checksum = ComputeChecksum(CurrentBuffer, BytesRead, 0);
779                         if (Checksum != CFData.Checksum) {
780                             CloseHandle(DestFile);
781                             HeapFree(GetProcessHeap(), 0, Buffer);
782                             DPRINT(MIN_TRACE, ("Bad checksum (is 0x%X, should be 0x%X).\n",
783                                 Checksum, CFData.Checksum));
784                             return CAB_STATUS_INVALID_CAB;
785                         }
786                     }
787 */
788                     TotalBytesRead += BytesRead;
789
790                     CurrentBuffer += BytesRead;
791
792                     if (CFData.UncompSize == 0) {
793                         if (lstrlen(DiskNext) == 0)
794                             return CAB_STATUS_NOFILE;
795
796                         /* CloseCabinet() will destroy all file entries so in case
797                            FileName refers to the FileName field of a CFFOLDER_NODE
798                            structure, we have to save a copy of the filename */
799                         lstrcpy(TempName, FileName);
800
801                         CloseCabinet();
802
803                         SetCabinetName(CabinetNext);
804
805                         OnDiskChange(CabinetNext, DiskNext);
806
807                         Status = Open();
808                         if (Status != CAB_STATUS_SUCCESS) 
809                             return Status;
810
811                         /* The first data block of the file will not be
812                            found as it is located in the previous file */
813                         Status = LocateFile(TempName, &File);
814                         if (Status == CAB_STATUS_NOFILE) {
815                             DPRINT(MID_TRACE, ("Cannot locate file (%d).\n", (UINT)Status));
816                             return Status;
817                         }
818
819                         /* The file is continued in the first data block in the folder */
820                         File->DataBlock = CurrentFolderNode->DataListHead;
821
822                         /* Search to start of file */
823                         SetFilePointer(FileHandle,
824                             File->DataBlock->AbsoluteOffset,
825                             NULL,
826                             FILE_BEGIN);
827                         if (GetLastError() != NO_ERROR) {
828                             DPRINT(MIN_TRACE, ("SetFilePointer() failed.\n"));
829                             return CAB_STATUS_INVALID_CAB;
830                         }
831
832                         DPRINT(MAX_TRACE, ("Continuing extraction of file at uncompressed offset (0x%X)  Size (%d bytes)  AO (0x%X)  UO (0x%X).\n",
833                             (UINT)File->File.FileOffset,
834                             (UINT)File->File.FileSize,
835                             (UINT)File->DataBlock->AbsoluteOffset,
836                             (UINT)File->DataBlock->UncompOffset));
837
838                         CurrentDataNode = File->DataBlock;
839                         ReuseBlock = TRUE;
840
841                         RestartSearch = TRUE;
842                     }
843                 } while (CFData.UncompSize == 0);
844
845                 DPRINT(MAX_TRACE, ("TotalBytesRead (%d).\n", TotalBytesRead));
846
847                 Status = Codec->Uncompress(OutputBuffer, Buffer, TotalBytesRead, &BytesToWrite);
848                 if (Status != CS_SUCCESS) {
849                     CloseHandle(DestFile);
850                     HeapFree(GetProcessHeap(), 0, Buffer);
851                     DPRINT(MID_TRACE, ("Cannot uncompress block.\n"));
852                     if (Status == CS_NOMEMORY)
853                         return CAB_STATUS_NOMEMORY;
854                     return CAB_STATUS_INVALID_CAB;
855                 }
856
857                 if (BytesToWrite != CFData.UncompSize) {
858                     DPRINT(MID_TRACE, ("BytesToWrite (%d) != CFData.UncompSize (%d)\n",
859                         BytesToWrite, CFData.UncompSize));
860                     return CAB_STATUS_INVALID_CAB;
861                 }
862
863                 BytesLeftInBlock = BytesToWrite;
864             } else {
865                 DPRINT(MAX_TRACE, ("Using same buffer. ReuseBlock (%d)\n", (UINT)ReuseBlock));
866
867                 BytesToWrite = BytesLeftInBlock;
868
869                                 DPRINT(MAX_TRACE, ("Seeking to absolute offset 0x%X.\n",
870                                         CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
871                     CurrentDataNode->Data.CompSize));
872
873                                 if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) != 
874                                         CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA))) {
875                                         CloseHandle(DestFile);
876                                         HeapFree(GetProcessHeap(), 0, Buffer);
877                                         DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
878                                         return CAB_STATUS_INVALID_CAB;
879                                 }
880
881                                 DPRINT(MAX_TRACE, ("CFData.CompSize 0x%X  CFData.UncompSize 0x%X.\n",
882                                         CFData.CompSize, CFData.UncompSize));
883
884                 /* Go to next data block */
885                 SetFilePointer(FileHandle,
886                     CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
887                     CurrentDataNode->Data.CompSize,
888                     NULL,
889                     FILE_BEGIN);
890                 if (GetLastError() != NO_ERROR) {
891                     DPRINT(MIN_TRACE, ("SetFilePointer() failed.\n"));
892                     return CAB_STATUS_INVALID_CAB;
893                 }
894
895                 ReuseBlock = FALSE;
896             }
897
898             if (Skip)
899                 BytesSkipped = (Offset - CurrentOffset);
900             else
901                 BytesSkipped = 0;
902
903                         BytesToWrite -= BytesSkipped;
904
905                         if (Size < BytesToWrite)
906                 BytesToWrite = Size;
907
908             DPRINT(MAX_TRACE, ("Offset (0x%X)  CurrentOffset (0x%X)  ToWrite (%d)  Skipped (%d)(%d)  Size (%d).\n",
909                 (UINT)Offset,
910                 (UINT)CurrentOffset,
911                 (UINT)BytesToWrite,
912                 (UINT)BytesSkipped, (UINT)Skip,
913                 (UINT)Size));
914
915             if (!WriteFile(DestFile, (PVOID)((ULONG)OutputBuffer + BytesSkipped),
916                 BytesToWrite, &BytesWritten, NULL) ||
917                 (BytesToWrite != BytesWritten)) {
918
919                                 DPRINT(MIN_TRACE, ("Status 0x%X.\n", GetLastError()));
920
921                 CloseHandle(DestFile);
922                 HeapFree(GetProcessHeap(), 0, Buffer);
923                 DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
924                 return CAB_STATUS_CANNOT_WRITE;
925             }
926             Size -= BytesToWrite;
927
928                         CurrentOffset += BytesToWrite;
929
930             /* Don't skip any more bytes */
931             Skip = FALSE;
932         } while (Size > 0);
933     }
934
935     CloseHandle(DestFile);
936
937     HeapFree(GetProcessHeap(), 0, Buffer);
938
939     return CAB_STATUS_SUCCESS;
940 }
941
942
943 VOID CCabinet::SelectCodec(ULONG Id)
944 /*
945  * FUNCTION: Selects codec engine to use
946  * ARGUMENTS:
947  *     Id = Codec identifier
948  */
949 {
950     if (CodecSelected) {
951         if (Id == CodecId)
952             return;
953
954         CodecSelected = FALSE;
955         delete Codec;
956     }
957
958     switch (Id) {
959     case CAB_CODEC_RAW:
960         Codec = new CRawCodec();
961         break;
962     case CAB_CODEC_MSZIP:
963         Codec = new CMSZipCodec();
964         break;
965     default:
966         return;
967     }
968
969     CodecId       = Id;
970     CodecSelected = TRUE;
971 }
972
973
974 #ifndef CAB_READ_ONLY
975
976 /* CAB write methods */
977
978 ULONG CCabinet::NewCabinet()
979 /*
980  * FUNCTION: Creates a new cabinet
981  * RETURNS:
982  *     Status of operation
983  */
984 {
985     ULONG Status;
986
987     CurrentDiskNumber = 0;
988
989     OutputBuffer = HeapAlloc(GetProcessHeap(), 0, CAB_BLOCKSIZE + 12); // This should be enough
990     InputBuffer  = HeapAlloc(GetProcessHeap(), 0, CAB_BLOCKSIZE + 12); // This should be enough
991     if ((!OutputBuffer) || (!InputBuffer)) {
992         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
993         return CAB_STATUS_NOMEMORY; 
994     }
995     CurrentIBuffer     = InputBuffer;
996     CurrentIBufferSize = 0;
997
998     CABHeader.Signature     = CAB_SIGNATURE;
999     CABHeader.Reserved1     = 0;            // Not used
1000     CABHeader.CabinetSize   = 0;            // Not yet known
1001     CABHeader.Reserved2     = 0;            // Not used
1002     CABHeader.Reserved3     = 0;            // Not used
1003     CABHeader.Version       = CAB_VERSION;
1004     CABHeader.FolderCount   = 0;            // Not yet known
1005     CABHeader.FileCount     = 0;            // Not yet known
1006     CABHeader.Flags         = 0;            // Not yet known
1007     // FIXME: Should be random
1008     CABHeader.SetID         = 0x534F;
1009     CABHeader.CabinetNumber = 0;
1010
1011
1012     TotalFolderSize = 0;
1013     TotalFileSize   = 0;
1014
1015     DiskSize = sizeof(CFHEADER);
1016
1017     InitCabinetHeader();
1018
1019     // NextFolderNumber is 0-based
1020     NextFolderNumber = 0;
1021
1022         CurrentFolderNode = NULL;
1023     Status = NewFolder();
1024     if (Status != CAB_STATUS_SUCCESS)
1025         return Status;
1026
1027     CurrentFolderNode->Folder.DataOffset = DiskSize - TotalHeaderSize;
1028
1029     ScratchFile = new CCFDATAStorage;
1030     if (!ScratchFile) {
1031         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1032         return CAB_STATUS_NOMEMORY;
1033     }
1034
1035     Status = ScratchFile->Create("~CAB.tmp");
1036
1037     CreateNewFolder = FALSE;
1038
1039     CreateNewDisk = FALSE;
1040
1041     PrevCabinetNumber = 0;
1042
1043     return Status;
1044 }
1045
1046
1047 ULONG CCabinet::NewDisk()
1048 /*
1049  * FUNCTION: Forces a new disk to be created
1050  * RETURNS:
1051  *     Status of operation
1052  */
1053 {
1054     // NextFolderNumber is 0-based
1055     NextFolderNumber = 1;
1056
1057     CreateNewDisk = FALSE;
1058
1059     DiskSize = sizeof(CFHEADER) + TotalFolderSize + TotalFileSize;
1060
1061     InitCabinetHeader();
1062
1063     CurrentFolderNode->TotalFolderSize = 0;
1064
1065     CurrentFolderNode->Folder.DataBlockCount = 0;
1066
1067     return CAB_STATUS_SUCCESS;
1068 }
1069
1070
1071 ULONG CCabinet::NewFolder()
1072 /*
1073  * FUNCTION: Forces a new folder to be created
1074  * RETURNS:
1075  *     Status of operation
1076  */
1077 {
1078     DPRINT(MAX_TRACE, ("Creating new folder.\n"));
1079
1080     CurrentFolderNode = NewFolderNode();
1081     if (!CurrentFolderNode) {
1082         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1083         return CAB_STATUS_NOMEMORY;
1084     }
1085
1086     switch (CodecId) {
1087     case CAB_CODEC_RAW:
1088         CurrentFolderNode->Folder.CompressionType = CAB_COMP_NONE;
1089         break;
1090     case CAB_CODEC_MSZIP:
1091         CurrentFolderNode->Folder.CompressionType = CAB_COMP_MSZIP;
1092         break;
1093     default:
1094         return CAB_STATUS_UNSUPPCOMP;
1095     }
1096
1097         /* FIXME: This won't work if no files are added to the new folder */
1098
1099     DiskSize += sizeof(CFFOLDER);
1100
1101     TotalFolderSize += sizeof(CFFOLDER);
1102
1103     NextFolderNumber++;
1104
1105     CABHeader.FolderCount++;
1106
1107     LastBlockStart = 0;
1108
1109     return CAB_STATUS_SUCCESS;
1110 }
1111
1112
1113 ULONG CCabinet::WriteFileToScratchStorage(PCFFILE_NODE FileNode)
1114 /*
1115  * FUNCTION: Writes a file to the scratch file
1116  * ARGUMENTS:
1117  *     FileNode = Pointer to file node
1118  * RETURNS:
1119  *     Status of operation
1120  */
1121 {
1122     DWORD BytesToRead;
1123     DWORD BytesRead;
1124     ULONG Status;
1125     DWORD Size;
1126
1127     if (!ContinueFile) {
1128         /* Try to open file */
1129         SourceFile = CreateFile(
1130             FileNode->FileName,      // Open this file
1131             GENERIC_READ,            // Open for reading
1132             FILE_SHARE_READ,         // Share for reading
1133             NULL,                    // No security
1134             OPEN_EXISTING,           // File must exist
1135             FILE_ATTRIBUTE_NORMAL,   // Normal file 
1136             NULL);                   // No attribute template
1137         if (SourceFile == INVALID_HANDLE_VALUE) {
1138             DPRINT(MID_TRACE, ("File not found (%s).\n", FileNode->FileName));
1139             return CAB_STATUS_NOFILE;
1140         }
1141
1142         if (CreateNewFolder) {
1143             /* There is always a new folder after
1144                a split file is completely stored */
1145             Status = NewFolder();
1146             if (Status != CAB_STATUS_SUCCESS)
1147                 return Status;
1148             CreateNewFolder = FALSE;
1149         }
1150
1151         /* Call OnAdd event handler */
1152         OnAdd(&FileNode->File, FileNode->FileName);
1153
1154         TotalBytesLeft = FileNode->File.FileSize;
1155
1156         FileNode->File.FileOffset        = CurrentFolderNode->UncompOffset;
1157         CurrentFolderNode->UncompOffset += TotalBytesLeft;
1158         FileNode->File.FileControlID     = NextFolderNumber - 1;
1159         CurrentFolderNode->Commit        = TRUE;
1160         PrevCabinetNumber                                = CurrentDiskNumber;
1161
1162         Size = sizeof(CFFILE) + lstrlen(GetFileName(FileNode->FileName)) + 1;
1163         CABHeader.FileTableOffset += Size;
1164         TotalFileSize += Size;
1165         DiskSize += Size;
1166     }
1167
1168     FileNode->Commit = TRUE;
1169
1170     if (TotalBytesLeft > 0) {
1171         do {
1172             if (TotalBytesLeft > (DWORD)CAB_BLOCKSIZE - CurrentIBufferSize)
1173                 BytesToRead = CAB_BLOCKSIZE - CurrentIBufferSize;
1174             else
1175                 BytesToRead = TotalBytesLeft;
1176
1177             if ((!ReadFile(SourceFile, CurrentIBuffer, BytesToRead, &BytesRead, NULL)
1178                 != CAB_STATUS_SUCCESS) || (BytesToRead != BytesRead)) {
1179                 DPRINT(MIN_TRACE, ("Cannot read from file. BytesToRead (%d)  BytesRead (%d)  CurrentIBufferSize (%d).\n",
1180                     BytesToRead, BytesRead, CurrentIBufferSize));
1181                 return CAB_STATUS_INVALID_CAB;
1182             }
1183
1184             (PUCHAR)CurrentIBuffer += BytesRead;
1185
1186             CurrentIBufferSize += (WORD)BytesRead;
1187
1188             if (CurrentIBufferSize == CAB_BLOCKSIZE) {
1189                 Status = WriteDataBlock();
1190                 if (Status != CAB_STATUS_SUCCESS)
1191                     return Status;
1192             }
1193             TotalBytesLeft -= BytesRead;
1194         } while ((TotalBytesLeft > 0) && (!CreateNewDisk));
1195     }
1196
1197     if (TotalBytesLeft == 0) {
1198         CloseHandle(SourceFile);
1199         FileNode->Delete = TRUE;
1200
1201         if (FileNode->File.FileControlID > CAB_FILE_MAX_FOLDER) {
1202             FileNode->File.FileControlID = CAB_FILE_CONTINUED;
1203             CurrentFolderNode->Delete = TRUE;
1204
1205             if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0)) {
1206                 Status = WriteDataBlock();
1207                 if (Status != CAB_STATUS_SUCCESS)
1208                     return Status;
1209             }
1210
1211             CreateNewFolder = TRUE;
1212         }
1213     } else {
1214         if (FileNode->File.FileControlID <= CAB_FILE_MAX_FOLDER)
1215             FileNode->File.FileControlID = CAB_FILE_SPLIT;
1216         else
1217             FileNode->File.FileControlID = CAB_FILE_PREV_NEXT;
1218     }
1219
1220     return CAB_STATUS_SUCCESS;
1221 }
1222
1223
1224 ULONG CCabinet::WriteDisk(DWORD MoreDisks)
1225 /*
1226  * FUNCTION: Forces the current disk to be written
1227  * ARGUMENTS:
1228  *     MoreDisks = TRUE if there is one or more disks after this disk
1229  * RETURNS:
1230  *     Status of operation
1231  */
1232 {
1233     PCFFILE_NODE FileNode;
1234     ULONG Status;
1235
1236     ContinueFile = FALSE;
1237     FileNode = FileListHead;
1238     while (FileNode != NULL) {
1239
1240         Status = WriteFileToScratchStorage(FileNode);
1241         if (Status != CAB_STATUS_SUCCESS)
1242             return Status;
1243         if (CreateNewDisk) {
1244             /* A data block could span more than two
1245                disks if MaxDiskSize is very small */
1246             while (CreateNewDisk) {
1247                 DPRINT(MAX_TRACE, ("Creating new disk.\n"));
1248                 CommitDisk(TRUE);
1249                 CloseDisk();
1250                 NewDisk();
1251
1252                 ContinueFile = TRUE;
1253                 CreateNewDisk = FALSE;
1254
1255                 DPRINT(MAX_TRACE, ("First on new disk. CurrentIBufferSize (%d)  CurrentOBufferSize (%d).\n", 
1256                     CurrentIBufferSize, CurrentOBufferSize));
1257
1258                 if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0)) {
1259                     Status = WriteDataBlock();
1260                     if (Status != CAB_STATUS_SUCCESS)
1261                         return Status;
1262                 }
1263             }
1264         } else {
1265             ContinueFile = FALSE;
1266             FileNode = FileNode->Next;
1267         }
1268     }
1269
1270     if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0)) {
1271         /* A data block could span more than two
1272            disks if MaxDiskSize is very small */
1273
1274         ASSERT(CreateNewDisk == FALSE);
1275
1276         do {
1277             if (CreateNewDisk) {
1278                 DPRINT(MID_TRACE, ("Creating new disk 2.\n"));
1279                 CommitDisk(TRUE);
1280                 CloseDisk();
1281                 NewDisk();
1282                 CreateNewDisk = FALSE;
1283
1284                 ASSERT(FileNode == FileListHead);
1285             }
1286
1287             if ((CurrentIBufferSize > 0) || (CurrentOBufferSize > 0)) {
1288                 Status = WriteDataBlock();
1289                 if (Status != CAB_STATUS_SUCCESS)
1290                     return Status;
1291             }
1292         } while (CreateNewDisk);
1293     }
1294     CommitDisk(MoreDisks);
1295
1296     return CAB_STATUS_SUCCESS;
1297 }
1298
1299
1300 ULONG CCabinet::CommitDisk(DWORD MoreDisks)
1301 /*
1302  * FUNCTION: Commits the current disk
1303  * ARGUMENTS:
1304  *     MoreDisks = TRUE if there is one or more disks after this disk
1305  * RETURNS:
1306  *     Status of operation
1307  */
1308 {
1309     PCFFOLDER_NODE FolderNode;
1310     ULONG Status;
1311
1312     OnCabinetName(CurrentDiskNumber, CabinetName);
1313
1314     /* Create file, fail if it already exists */
1315     FileHandle = CreateFile(CabinetName, // Create this file
1316         GENERIC_WRITE,                   // Open for writing
1317         0,                               // No sharing
1318         NULL,                            // No security
1319         CREATE_NEW,                      // New file only
1320         FILE_ATTRIBUTE_NORMAL,           // Normal file
1321         NULL);                           // No attribute template
1322     if (FileHandle == INVALID_HANDLE_VALUE) {
1323         DWORD Status;
1324         /* If file exists, ask to overwrite file */
1325         if (((Status = GetLastError()) == ERROR_FILE_EXISTS) &&
1326             (OnOverwrite(NULL, CabinetName))) {
1327
1328             /* Create cabinet file, overwrite if it already exists */
1329             FileHandle = CreateFile(CabinetName, // Create this file
1330                 GENERIC_WRITE,                   // Open for writing
1331                 0,                               // No sharing
1332                 NULL,                            // No security
1333                 TRUNCATE_EXISTING,               // Truncate the file
1334                 FILE_ATTRIBUTE_NORMAL,           // Normal file
1335                 NULL);                           // No attribute template
1336             if (FileHandle == INVALID_HANDLE_VALUE)
1337                 return CAB_STATUS_CANNOT_CREATE;
1338         } else {
1339             if (Status == ERROR_FILE_EXISTS)
1340                 return CAB_STATUS_FILE_EXISTS;
1341             else
1342                 return CAB_STATUS_CANNOT_CREATE;
1343         }
1344     }
1345
1346     WriteCabinetHeader(MoreDisks);
1347
1348     Status = WriteFolderEntries();
1349     if (Status != CAB_STATUS_SUCCESS)
1350         return Status;
1351
1352     /* Write file entries */
1353     WriteFileEntries();
1354
1355     /* Write data blocks */
1356     FolderNode = FolderListHead;
1357     while (FolderNode != NULL) {
1358         if (FolderNode->Commit) {
1359             Status = CommitDataBlocks(FolderNode);
1360             if (Status != CAB_STATUS_SUCCESS)
1361                 return Status;
1362             /* Remove data blocks for folder */
1363             DestroyDataNodes(FolderNode);
1364         }
1365         FolderNode = FolderNode->Next;
1366     }
1367
1368     CloseHandle(FileHandle);
1369
1370     ScratchFile->Truncate();
1371
1372     return CAB_STATUS_SUCCESS;
1373 }
1374
1375
1376 ULONG CCabinet::CloseDisk()
1377 /*
1378  * FUNCTION: Closes the current disk
1379  * RETURNS:
1380  *     Status of operation
1381  */
1382 {
1383     DestroyDeletedFileNodes();
1384
1385     /* Destroy folder nodes that are completely stored */
1386     DestroyDeletedFolderNodes();
1387
1388     CurrentDiskNumber++;
1389
1390     return CAB_STATUS_SUCCESS;
1391 }
1392
1393
1394 ULONG CCabinet::CloseCabinet()
1395 /*
1396  * FUNCTION: Closes the current cabinet
1397  * RETURNS:
1398  *     Status of operation
1399  */
1400 {
1401     PCFFOLDER_NODE PrevNode;
1402     PCFFOLDER_NODE NextNode;
1403     ULONG Status;
1404
1405     DestroyFileNodes();
1406
1407     DestroyFolderNodes();
1408
1409     if (InputBuffer) {
1410         HeapFree(GetProcessHeap(), 0, InputBuffer);
1411         InputBuffer = NULL;
1412     }
1413
1414     if (OutputBuffer) {
1415         HeapFree(GetProcessHeap(), 0, OutputBuffer);
1416         OutputBuffer = NULL;
1417     }
1418
1419     Close();
1420
1421     if (ScratchFile) {
1422         Status = ScratchFile->Destroy();
1423         delete ScratchFile;
1424         return Status;
1425     }
1426
1427     return CAB_STATUS_SUCCESS;
1428 }
1429
1430
1431 ULONG CCabinet::AddFile(LPTSTR FileName)
1432 /*
1433  * FUNCTION: Adds a file to the current disk
1434  * ARGUMENTS:
1435  *     FileName = Pointer to string with file name (full path)
1436  * RETURNS:
1437  *     Status of operation
1438  */
1439 {
1440     HANDLE SrcFile;
1441     FILETIME FileTime;
1442     PCFFILE_NODE FileNode;
1443
1444     FileNode = NewFileNode();
1445     if (!FileNode) {
1446         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1447         return CAB_STATUS_NOMEMORY;
1448     }
1449
1450         FileNode->FolderNode = CurrentFolderNode;
1451
1452     FileNode->FileName = (LPTSTR)HeapAlloc(GetProcessHeap(),
1453         0, lstrlen(FileName) + 1);
1454     lstrcpy(FileNode->FileName, FileName);
1455
1456     /* Try to open file */
1457     SrcFile = CreateFile(
1458         FileNode->FileName,      // Open this file
1459         GENERIC_READ,            // Open for reading
1460         FILE_SHARE_READ,         // Share for reading
1461         NULL,                    // No security
1462         OPEN_EXISTING,           // File must exist
1463         FILE_ATTRIBUTE_NORMAL,   // Normal file 
1464         NULL);                   // No attribute template
1465     if (SrcFile == INVALID_HANDLE_VALUE) {
1466         DPRINT(MID_TRACE, ("File not found (%s).\n", FileNode->FileName));
1467         return CAB_STATUS_CANNOT_OPEN;
1468     }
1469
1470     /* FIXME: Check for and handle large files (>= 2GB) */
1471     FileNode->File.FileSize = GetFileSize(SrcFile, NULL);
1472     if (GetLastError() != NO_ERROR) {
1473         DPRINT(MIN_TRACE, ("Cannot read from file.\n"));
1474         return CAB_STATUS_CANNOT_READ;
1475     }
1476
1477     if (GetFileTime(SrcFile, NULL, &FileTime, NULL))
1478         FileTimeToDosDateTime(&FileTime,
1479             &FileNode->File.FileDate,
1480             &FileNode->File.FileTime);
1481
1482     GetAttributesOnFile(FileNode);
1483
1484     CloseHandle(SrcFile);
1485
1486     return CAB_STATUS_SUCCESS;
1487 }
1488
1489
1490 VOID CCabinet::SetMaxDiskSize(DWORD Size)
1491 /*
1492  * FUNCTION: Sets the maximum size of the current disk
1493  * ARGUMENTS:
1494  *     Size = Maximum size of current disk (0 means no maximum size)
1495  */
1496 {
1497     MaxDiskSize = Size;
1498 }
1499
1500 #endif /* CAB_READ_ONLY */
1501
1502
1503 /* Default event handlers */
1504
1505 BOOL CCabinet::OnOverwrite(PCFFILE File,
1506                            LPTSTR FileName)
1507 /*
1508  * FUNCTION: Called when extracting a file and it already exists
1509  * ARGUMENTS:
1510  *     File     = Pointer to CFFILE for file being extracted
1511  *     FileName = Pointer to buffer with name of file (full path)
1512  * RETURNS
1513  *     TRUE if the file should be overwritten, FALSE if not
1514  */
1515 {
1516     return FALSE;
1517 }
1518
1519
1520 VOID CCabinet::OnExtract(PCFFILE File,
1521                          LPTSTR FileName)
1522 /*
1523  * FUNCTION: Called just before extracting a file
1524  * ARGUMENTS:
1525  *     File     = Pointer to CFFILE for file being extracted
1526  *     FileName = Pointer to buffer with name of file (full path)
1527  */
1528 {
1529 }
1530
1531
1532 VOID CCabinet::OnDiskChange(LPTSTR CabinetName,
1533                             LPTSTR DiskLabel)
1534 /*
1535  * FUNCTION: Called when a new disk is to be processed
1536  * ARGUMENTS:
1537  *     CabinetName = Pointer to buffer with name of cabinet
1538  *     DiskLabel   = Pointer to buffer with label of disk
1539  */
1540 {
1541 }
1542
1543
1544 #ifndef CAB_READ_ONLY
1545
1546 VOID CCabinet::OnAdd(PCFFILE File,
1547                      LPTSTR FileName)
1548 /*
1549  * FUNCTION: Called just before adding a file to a cabinet
1550  * ARGUMENTS:
1551  *     File     = Pointer to CFFILE for file being added
1552  *     FileName = Pointer to buffer with name of file (full path)
1553  */
1554 {
1555 }
1556
1557
1558 BOOL CCabinet::OnDiskLabel(ULONG Number, LPTSTR Label)
1559 /*
1560  * FUNCTION: Called when a disk needs a label
1561  * ARGUMENTS:
1562  *     Number = Cabinet number that needs a label
1563  *     Label  = Pointer to buffer to place label of disk
1564  * RETURNS:
1565  *     TRUE if a disk label was returned, FALSE if not
1566  */
1567 {
1568     return FALSE;
1569 }
1570
1571
1572 BOOL CCabinet::OnCabinetName(ULONG Number, LPTSTR Name)
1573 /*
1574  * FUNCTION: Called when a cabinet needs a name
1575  * ARGUMENTS:
1576  *     Number = Disk number that needs a name
1577  *     Name   = Pointer to buffer to place name of cabinet
1578  * RETURNS:
1579  *     TRUE if a cabinet name was returned, FALSE if not
1580  */
1581 {
1582     return FALSE;
1583 }
1584
1585 #endif /* CAB_READ_ONLY */
1586
1587 PCFFOLDER_NODE CCabinet::LocateFolderNode(DWORD Index)
1588 /*
1589  * FUNCTION: Locates a folder node
1590  * ARGUMENTS:
1591  *     Index = Folder index
1592  * RETURNS:
1593  *     Pointer to folder node or NULL if the folder node was not found
1594  */
1595 {
1596     PCFFOLDER_NODE Node;
1597
1598     switch (Index) {
1599     case CAB_FILE_SPLIT:
1600         return FolderListTail;
1601
1602     case CAB_FILE_CONTINUED:
1603     case CAB_FILE_PREV_NEXT:
1604         return FolderListHead;
1605     }
1606
1607     Node = FolderListHead;
1608     while (Node != NULL) {
1609         if (Node->Index == Index)
1610             return Node;
1611         Node = Node->Next;
1612     }
1613     return NULL;
1614 }
1615
1616
1617 ULONG CCabinet::GetAbsoluteOffset(PCFFILE_NODE File)
1618 /*
1619  * FUNCTION: Returns the absolute offset of a file
1620  * ARGUMENTS:
1621  *     File = Pointer to CFFILE_NODE structure for file
1622  * RETURNS:
1623  *     Status of operation
1624  */
1625 {
1626     PCFDATA_NODE Node;
1627
1628     DPRINT(MAX_TRACE, ("FileName '%s'  FileOffset (0x%X)  FileSize (%d).\n",
1629         (LPTSTR)File->FileName,
1630         (UINT)File->File.FileOffset,
1631         (UINT)File->File.FileSize));
1632
1633     Node = CurrentFolderNode->DataListHead;
1634     while (Node != NULL) {
1635
1636         DPRINT(MAX_TRACE, ("GetAbsoluteOffset(): Comparing (0x%X, 0x%X) (%d).\n",
1637             (UINT)Node->UncompOffset,
1638             (UINT)Node->UncompOffset + Node->Data.UncompSize,
1639             (UINT)Node->Data.UncompSize));
1640
1641         /* Node->Data.UncompSize will be 0 if the block is split
1642            (ie. it is the last block in this cabinet) */
1643         if ((Node->Data.UncompSize == 0) ||
1644             ((File->File.FileOffset >= Node->UncompOffset) &&
1645             (File->File.FileOffset < Node->UncompOffset +
1646             Node->Data.UncompSize))) {
1647                 File->DataBlock = Node;
1648                 return CAB_STATUS_SUCCESS;
1649         }
1650
1651         Node = Node->Next;
1652     }
1653     return CAB_STATUS_INVALID_CAB;
1654 }
1655
1656
1657 ULONG CCabinet::LocateFile(LPTSTR FileName,
1658                            PCFFILE_NODE *File)
1659 /*
1660  * FUNCTION: Locates a file in the cabinet
1661  * ARGUMENTS:
1662  *     FileName = Pointer to string with name of file to locate
1663  *     File     = Address of pointer to CFFILE_NODE structure to fill
1664  * RETURNS:
1665  *     Status of operation
1666  * NOTES:
1667  *     Current folder is set to the folder of the file
1668  */
1669 {
1670     PCFFILE_NODE Node;
1671     ULONG Status;
1672
1673     DPRINT(MAX_TRACE, ("FileName '%s'\n", FileName));
1674
1675     Node = FileListHead;
1676     while (Node != NULL) {
1677         if (lstrcmpi(FileName, Node->FileName) == 0) {
1678
1679             CurrentFolderNode = LocateFolderNode(Node->File.FileControlID);
1680             if (!CurrentFolderNode) {
1681                 DPRINT(MID_TRACE, ("Folder with index number (%d) not found.\n",
1682                     (UINT)Node->File.FileControlID));
1683                 return CAB_STATUS_INVALID_CAB;
1684             }
1685
1686             if (Node->DataBlock == NULL) {
1687                 Status = GetAbsoluteOffset(Node);
1688             } else
1689                 Status = CAB_STATUS_SUCCESS;
1690             *File = Node;
1691             return Status;
1692         }
1693         Node = Node->Next;
1694     }
1695     return CAB_STATUS_NOFILE;
1696 }
1697
1698
1699 ULONG CCabinet::ReadString(LPTSTR String, DWORD MaxLength)
1700 /*
1701  * FUNCTION: Reads a NULL-terminated string from the cabinet
1702  * ARGUMENTS:
1703  *     String    = Pointer to buffer to place string
1704  *     MaxLength = Maximum length of string
1705  * RETURNS:
1706  *     Status of operation
1707  */
1708 {
1709     DWORD BytesRead;
1710     DWORD Offset;
1711     ULONG Status;
1712     DWORD Size;
1713     BOOL Found;
1714
1715     Offset = 0;
1716     Found  = FALSE;
1717     do {
1718         Size = ((Offset + 32) <= MaxLength)? 32 : MaxLength - Offset;
1719
1720         if (Size == 0) {
1721             DPRINT(MIN_TRACE, ("Too long a filename.\n"));
1722             return CAB_STATUS_INVALID_CAB;
1723         }
1724
1725         Status = ReadBlock(&String[Offset], Size, &BytesRead);
1726         if ((Status != CAB_STATUS_SUCCESS) || (BytesRead != Size)) {
1727             DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
1728             return CAB_STATUS_INVALID_CAB;
1729         }
1730
1731         for (Size = Offset; Size < Offset + BytesRead; Size++) {
1732             if (String[Size] == '\0') {
1733                 Found = TRUE;
1734                 break;
1735             }
1736         }
1737
1738         Offset += BytesRead;
1739
1740     } while (!Found);
1741
1742     /* Back up some bytes */
1743     Size = (BytesRead - Size) - 1;
1744     SetFilePointer(FileHandle, -(LONG)Size, NULL, FILE_CURRENT);
1745     if (GetLastError() != NO_ERROR) {
1746         DPRINT(MIN_TRACE, ("SetFilePointer() failed.\n"));
1747         return CAB_STATUS_INVALID_CAB;
1748     }
1749     return CAB_STATUS_SUCCESS;
1750 }
1751
1752
1753 ULONG CCabinet::ReadFileTable()
1754 /*
1755  * FUNCTION: Reads the file table from the cabinet file
1756  * RETURNS:
1757  *     Status of operation
1758  */
1759 {
1760     ULONG i;
1761     ULONG Status;
1762     DWORD BytesRead;
1763     PCFFILE_NODE File;
1764
1765     DPRINT(MAX_TRACE, ("Reading file table at absolute offset (0x%X).\n",
1766         CABHeader.FileTableOffset));
1767
1768     /* Seek to file table */
1769     SetFilePointer(FileHandle, CABHeader.FileTableOffset, NULL, FILE_BEGIN);
1770     if (GetLastError() != NO_ERROR) {
1771         DPRINT(MIN_TRACE, ("SetFilePointer() failed.\n"));
1772         return CAB_STATUS_INVALID_CAB;
1773     }
1774
1775     for (i = 0; i < CABHeader.FileCount; i++) {
1776         File = NewFileNode();
1777         if (!File) {
1778             DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1779             return CAB_STATUS_NOMEMORY;
1780         }
1781
1782         if ((Status = ReadBlock(&File->File, sizeof(CFFILE),
1783             &BytesRead)) != CAB_STATUS_SUCCESS) {
1784             DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
1785             return CAB_STATUS_INVALID_CAB;
1786         }
1787
1788         File->FileName = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH);
1789         if (!File->FileName) {
1790             DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1791             return CAB_STATUS_NOMEMORY;
1792         }
1793
1794         /* Read file name */
1795         Status = ReadString(File->FileName, MAX_PATH);
1796         if (Status != CAB_STATUS_SUCCESS)
1797             return Status;
1798
1799         DPRINT(MAX_TRACE, ("Found file '%s' at uncompressed offset (0x%X).  Size (%d bytes)  ControlId (0x%X).\n",
1800             (LPTSTR)File->FileName,
1801             (UINT)File->File.FileOffset,
1802             (UINT)File->File.FileSize,
1803             (UINT)File->File.FileControlID));
1804
1805     }
1806     return CAB_STATUS_SUCCESS;
1807 }
1808
1809
1810 ULONG CCabinet::ReadDataBlocks(PCFFOLDER_NODE FolderNode)
1811 /*
1812  * FUNCTION: Reads all CFDATA blocks for a folder from the cabinet file
1813  * ARGUMENTS:
1814  *     FolderNode = Pointer to CFFOLDER_NODE structure for folder
1815  * RETURNS:
1816  *     Status of operation
1817  */
1818 {
1819     DWORD AbsoluteOffset;
1820     DWORD UncompOffset;
1821     PCFDATA_NODE Node;
1822     DWORD BytesRead;
1823     ULONG Status;
1824     ULONG i;
1825
1826     DPRINT(MAX_TRACE, ("Reading data blocks for folder (%d)  at absolute offset (0x%X).\n",
1827         FolderNode->Index, FolderNode->Folder.DataOffset));
1828
1829     AbsoluteOffset = FolderNode->Folder.DataOffset;
1830     UncompOffset   = FolderNode->UncompOffset;
1831
1832     for (i = 0; i < FolderNode->Folder.DataBlockCount; i++) {
1833         Node = NewDataNode(FolderNode);
1834         if (!Node) {
1835             DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
1836             return CAB_STATUS_NOMEMORY;
1837         }
1838
1839         /* Seek to data block */
1840         SetFilePointer(FileHandle, AbsoluteOffset, NULL, FILE_BEGIN);
1841         if (GetLastError() != NO_ERROR) {
1842             DPRINT(MIN_TRACE, ("SetFilePointer() failed.\n"));
1843             return CAB_STATUS_INVALID_CAB;
1844         }
1845
1846         if ((Status = ReadBlock(&Node->Data, sizeof(CFDATA),
1847             &BytesRead)) != CAB_STATUS_SUCCESS) {
1848             DPRINT(MIN_TRACE, ("Cannot read from file (%d).\n", (UINT)Status));
1849             return CAB_STATUS_INVALID_CAB;
1850         }
1851
1852         DPRINT(MAX_TRACE, ("AbsOffset (0x%X)  UncompOffset (0x%X)  Checksum (0x%X)  CompSize (%d)  UncompSize (%d).\n",
1853             (UINT)AbsoluteOffset,
1854             (UINT)UncompOffset,
1855             (UINT)Node->Data.Checksum,
1856             (UINT)Node->Data.CompSize,
1857             (UINT)Node->Data.UncompSize));
1858
1859         Node->AbsoluteOffset = AbsoluteOffset;
1860         Node->UncompOffset   = UncompOffset;
1861
1862         AbsoluteOffset += sizeof(CFDATA) + Node->Data.CompSize;
1863         UncompOffset   += Node->Data.UncompSize;
1864     }
1865
1866     FolderUncompSize = UncompOffset;
1867
1868     return CAB_STATUS_SUCCESS;
1869 }
1870
1871
1872 PCFFOLDER_NODE CCabinet::NewFolderNode()
1873 /*
1874  * FUNCTION: Creates a new folder node
1875  * RETURNS:
1876  *     Pointer to node if there was enough free memory available, otherwise NULL
1877  */
1878 {
1879     PCFFOLDER_NODE Node;
1880
1881     Node = (PCFFOLDER_NODE)HeapAlloc(GetProcessHeap(),
1882         0, sizeof(CFFOLDER_NODE));
1883     if (!Node)
1884         return NULL;
1885
1886     ZeroMemory(Node, sizeof(CFFOLDER_NODE));
1887
1888     Node->Folder.CompressionType = CAB_COMP_NONE;
1889
1890     Node->Prev = FolderListTail;
1891
1892     if (FolderListTail != NULL) {
1893         FolderListTail->Next = Node;
1894     } else {
1895         FolderListHead = Node;
1896     }
1897     FolderListTail = Node;
1898
1899     return Node;
1900 }
1901
1902
1903 PCFFILE_NODE CCabinet::NewFileNode()
1904 /*
1905  * FUNCTION: Creates a new file node
1906  * ARGUMENTS:
1907  *     FolderNode = Pointer to folder node to bind file to
1908  * RETURNS:
1909  *     Pointer to node if there was enough free memory available, otherwise NULL
1910  */
1911 {
1912     PCFFILE_NODE Node;
1913
1914     Node = (PCFFILE_NODE)HeapAlloc(GetProcessHeap(),
1915         0, sizeof(CFFILE_NODE));
1916     if (!Node)
1917         return NULL;
1918
1919     ZeroMemory(Node, sizeof(CFFILE_NODE));
1920
1921     Node->Prev = FileListTail;
1922
1923     if (FileListTail != NULL) {
1924         FileListTail->Next = Node;
1925     } else {
1926         FileListHead = Node;
1927     }
1928     FileListTail = Node;
1929
1930     return Node;
1931 }
1932
1933
1934 PCFDATA_NODE CCabinet::NewDataNode(PCFFOLDER_NODE FolderNode)
1935 /*
1936  * FUNCTION: Creates a new data block node
1937  * ARGUMENTS:
1938  *     FolderNode = Pointer to folder node to bind data block to
1939  * RETURNS:
1940  *     Pointer to node if there was enough free memory available, otherwise NULL
1941  */
1942 {
1943     PCFDATA_NODE Node;
1944
1945     Node = (PCFDATA_NODE)HeapAlloc(GetProcessHeap(),
1946         0, sizeof(CFDATA_NODE));
1947     if (!Node)
1948         return NULL;
1949
1950     ZeroMemory(Node, sizeof(CFDATA_NODE));
1951
1952     Node->Prev = FolderNode->DataListTail;
1953
1954     if (FolderNode->DataListTail != NULL) {
1955         FolderNode->DataListTail->Next = Node;
1956     } else {
1957         FolderNode->DataListHead = Node;
1958     }
1959     FolderNode->DataListTail = Node;
1960
1961     return Node;
1962 }
1963
1964
1965 VOID CCabinet::DestroyDataNodes(PCFFOLDER_NODE FolderNode)
1966 /*
1967  * FUNCTION: Destroys data block nodes bound to a folder node
1968  * ARGUMENTS:
1969  *     FolderNode = Pointer to folder node
1970  */
1971 {
1972     PCFDATA_NODE PrevNode;
1973     PCFDATA_NODE NextNode;
1974
1975     NextNode = FolderNode->DataListHead;
1976     while (NextNode != NULL) {
1977         PrevNode = NextNode->Next;
1978         HeapFree(GetProcessHeap(), 0, NextNode);
1979         NextNode = PrevNode;
1980     }
1981     FolderNode->DataListHead = NULL;
1982     FolderNode->DataListTail = NULL;
1983 }
1984
1985
1986 VOID CCabinet::DestroyFileNodes()
1987 /*
1988  * FUNCTION: Destroys file nodes
1989  * ARGUMENTS:
1990  *     FolderNode = Pointer to folder node
1991  */
1992 {
1993     PCFFILE_NODE PrevNode;
1994     PCFFILE_NODE NextNode;
1995
1996     NextNode = FileListHead;
1997     while (NextNode != NULL) {
1998         PrevNode = NextNode->Next;
1999         if (NextNode->FileName)
2000             HeapFree(GetProcessHeap(), 0, NextNode->FileName);
2001         HeapFree(GetProcessHeap(), 0, NextNode);
2002         NextNode = PrevNode;
2003     }
2004     FileListHead = NULL;
2005     FileListTail = NULL;
2006 }
2007
2008
2009 VOID CCabinet::DestroyDeletedFileNodes()
2010 /*
2011  * FUNCTION: Destroys file nodes that are marked for deletion
2012  */
2013 {
2014     PCFFILE_NODE CurNode;
2015     PCFFILE_NODE NextNode;
2016
2017     CurNode = FileListHead;
2018     while (CurNode != NULL) {
2019         NextNode = CurNode->Next;
2020
2021         if (CurNode->Delete) {
2022             if (CurNode->Prev != NULL) {
2023                 CurNode->Prev->Next = CurNode->Next;
2024             } else {
2025                 FileListHead = CurNode->Next;
2026                 if (FileListHead)
2027                     FileListHead->Prev = NULL;
2028             }
2029
2030             if (CurNode->Next != NULL) {
2031                 CurNode->Next->Prev = CurNode->Prev;
2032             } else {
2033                 FileListTail = CurNode->Prev;
2034                 if (FileListTail)
2035                     FileListTail->Next = NULL;
2036             }
2037
2038             DPRINT(MAX_TRACE, ("Deleting file: '%s'\n", CurNode->FileName));
2039
2040             TotalFileSize -= (sizeof(CFFILE) + lstrlen(GetFileName(CurNode->FileName)) + 1);
2041
2042             if (CurNode->FileName)
2043                 HeapFree(GetProcessHeap(), 0, CurNode->FileName);
2044             HeapFree(GetProcessHeap(), 0, CurNode);
2045         }
2046         CurNode = NextNode;
2047     }
2048 }
2049
2050
2051 VOID CCabinet::DestroyFolderNodes()
2052 /*
2053  * FUNCTION: Destroys folder nodes
2054  */
2055 {
2056     PCFFOLDER_NODE PrevNode;
2057     PCFFOLDER_NODE NextNode;
2058
2059     NextNode = FolderListHead;
2060     while (NextNode != NULL) {
2061         PrevNode = NextNode->Next;
2062         DestroyDataNodes(NextNode);
2063         HeapFree(GetProcessHeap(), 0, NextNode);
2064         NextNode = PrevNode;
2065     }
2066     FolderListHead = NULL;
2067     FolderListTail = NULL;
2068 }
2069
2070
2071 VOID CCabinet::DestroyDeletedFolderNodes()
2072 /*
2073  * FUNCTION: Destroys folder nodes that are marked for deletion
2074  */
2075 {
2076     PCFFOLDER_NODE CurNode;
2077     PCFFOLDER_NODE NextNode;
2078
2079     CurNode = FolderListHead;
2080     while (CurNode != NULL) {
2081         NextNode = CurNode->Next;
2082
2083         if (CurNode->Delete) {
2084             if (CurNode->Prev != NULL) {
2085                 CurNode->Prev->Next = CurNode->Next;
2086             } else {
2087                 FolderListHead = CurNode->Next;
2088                 if (FolderListHead)
2089                     FolderListHead->Prev = NULL;
2090             }
2091
2092             if (CurNode->Next != NULL) {
2093                 CurNode->Next->Prev = CurNode->Prev;
2094             } else {
2095                 FolderListTail = CurNode->Prev;
2096                 if (FolderListTail)
2097                     FolderListTail->Next = NULL;
2098             }
2099
2100             DestroyDataNodes(CurNode);
2101             HeapFree(GetProcessHeap(), 0, CurNode);
2102
2103             TotalFolderSize -= sizeof(CFFOLDER);
2104         }
2105         CurNode = NextNode;
2106     }
2107 }
2108
2109
2110 ULONG CCabinet::ComputeChecksum(PVOID Buffer,
2111                                 UINT Size,
2112                                 ULONG Seed)
2113 /*
2114  * FUNCTION: Computes checksum for data block
2115  * ARGUMENTS:
2116  *     Buffer = Pointer to data buffer
2117  *     Size   = Length of data buffer
2118  *     Seed   = Previously computed checksum
2119  * RETURNS:
2120  *     Checksum of buffer
2121  */
2122 {
2123     INT UlongCount; // Number of ULONGs in block
2124     ULONG Checksum; // Checksum accumulator
2125     PBYTE pb;
2126     ULONG ul;
2127
2128     /* FIXME: Doesn't seem to be correct. EXTRACT.EXE
2129        won't accept checksums computed by this routine */
2130
2131     DPRINT(MIN_TRACE, ("Checksumming buffer (0x%X)  Size (%d)\n", (UINT)Buffer, Size));
2132
2133     UlongCount = Size / 4;              // Number of ULONGs
2134     Checksum   = Seed;                  // Init checksum
2135     pb         = (PBYTE)Buffer;         // Start at front of data block
2136
2137     /* Checksum integral multiple of ULONGs */
2138     while (UlongCount-- > 0) {
2139         /* NOTE: Build ULONG in big/little-endian independent manner */
2140         ul = *pb++;                     // Get low-order byte
2141         ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
2142         ul |= (((ULONG)(*pb++)) << 16); // Add 3nd byte
2143         ul |= (((ULONG)(*pb++)) << 24); // Add 4th byte
2144
2145         Checksum ^= ul;                 // Update checksum
2146     }
2147
2148     /* Checksum remainder bytes */
2149     ul = 0;
2150     switch (Size % 4) {
2151         case 3:
2152             ul |= (((ULONG)(*pb++)) << 16); // Add 3rd byte
2153         case 2:
2154             ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
2155         case 1:
2156             ul |= *pb++;                    // Get low-order byte
2157         default:
2158             break;
2159     }
2160     Checksum ^= ul;                         // Update checksum
2161
2162     /* Return computed checksum */
2163     return Checksum;
2164 }
2165
2166
2167 ULONG CCabinet::ReadBlock(PVOID Buffer,
2168                           DWORD Size,
2169                           PDWORD BytesRead)
2170 /*
2171  * FUNCTION: Read a block of data from file
2172  * ARGUMENTS:
2173  *     Buffer    = Pointer to data buffer
2174  *     Size      = Length of data buffer
2175  *     BytesRead = Pointer to DWORD that on return will contain
2176  *                 number of bytes read
2177  * RETURNS:
2178  *     Status of operation
2179  */
2180 {
2181     if (!ReadFile(FileHandle, Buffer, Size, BytesRead, NULL))
2182         return CAB_STATUS_INVALID_CAB;
2183     return CAB_STATUS_SUCCESS;
2184 }
2185
2186 #ifndef CAB_READ_ONLY
2187
2188 ULONG CCabinet::InitCabinetHeader()
2189 /*
2190  * FUNCTION: Initializes cabinet header and optional fields
2191  * RETURNS:
2192  *     Status of operation
2193  */
2194 {
2195     PCFFOLDER_NODE FolderNode;
2196     PCFFILE_NODE FileNode;
2197     DWORD TotalSize;
2198     DWORD Size;
2199
2200     CABHeader.FileTableOffset = 0;    // Not known yet
2201     CABHeader.FolderCount     = 0;    // Not known yet
2202     CABHeader.FileCount       = 0;    // Not known yet
2203     CABHeader.Flags           = 0;    // Not known yet
2204
2205     CABHeader.CabinetNumber = CurrentDiskNumber;
2206     
2207     if ((CurrentDiskNumber > 0) && (OnCabinetName(PrevCabinetNumber, CabinetPrev))) {
2208         CABHeader.Flags |= CAB_FLAG_HASPREV;
2209         if (!OnDiskLabel(PrevCabinetNumber, DiskPrev))
2210             lstrcpy(CabinetPrev, "");
2211     }
2212
2213     if (OnCabinetName(CurrentDiskNumber + 1, CabinetNext)) {
2214         CABHeader.Flags |= CAB_FLAG_HASNEXT;
2215         if (!OnDiskLabel(CurrentDiskNumber + 1, DiskNext))
2216             lstrcpy(DiskNext, "");
2217     }
2218
2219     TotalSize = 0;
2220
2221     if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0) {
2222
2223         DPRINT(MAX_TRACE, ("CabinetPrev '%s'.\n", CabinetPrev));
2224
2225         /* Calculate size of name of previous cabinet */
2226         TotalSize += lstrlen(CabinetPrev) + 1;
2227
2228         /* Calculate size of label of previous disk */
2229         TotalSize += lstrlen(DiskPrev) + 1;
2230     }
2231
2232     if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0) {
2233
2234         DPRINT(MAX_TRACE, ("CabinetNext '%s'.\n", CabinetNext));
2235
2236         /* Calculate size of name of next cabinet */
2237         Size = lstrlen(CabinetNext) + 1;
2238         TotalSize += Size;
2239         NextFieldsSize = Size;
2240
2241         /* Calculate size of label of next disk */
2242         Size = lstrlen(DiskNext) + 1;
2243         TotalSize += Size;
2244         NextFieldsSize += Size;
2245     } else
2246         NextFieldsSize = 0;
2247
2248     DiskSize += TotalSize;
2249
2250     /* FIXME: Add cabinet reserved area size */
2251     TotalHeaderSize = sizeof(CFHEADER) + TotalSize;
2252
2253     return CAB_STATUS_SUCCESS;
2254 }
2255
2256
2257 ULONG CCabinet::WriteCabinetHeader(BOOL MoreDisks)
2258 /*
2259  * FUNCTION: Writes the cabinet header and optional fields
2260  * ARGUMENTS:
2261  *     MoreDisks = TRUE if next cabinet name should be included
2262  * RETURNS:
2263  *     Status of operation
2264  */
2265 {
2266     PCFFOLDER_NODE FolderNode;
2267     PCFFILE_NODE FileNode;
2268     DWORD BytesWritten;
2269     DWORD Size;
2270
2271     if (MoreDisks) {
2272         CABHeader.Flags |= CAB_FLAG_HASNEXT;
2273         Size = TotalHeaderSize;
2274     } else {
2275         CABHeader.Flags &= ~CAB_FLAG_HASNEXT;
2276         DiskSize -= NextFieldsSize;
2277         Size = TotalHeaderSize - NextFieldsSize;
2278     }
2279
2280     /* Set absolute folder offsets */
2281     BytesWritten = Size + TotalFolderSize + TotalFileSize;
2282     CABHeader.FolderCount = 0;
2283     FolderNode = FolderListHead;
2284     while (FolderNode != NULL) {
2285         FolderNode->Folder.DataOffset = BytesWritten;
2286
2287         BytesWritten += FolderNode->TotalFolderSize;
2288
2289         CABHeader.FolderCount++;
2290
2291         FolderNode = FolderNode->Next;
2292     }
2293
2294     /* Set absolute offset of file table */
2295     CABHeader.FileTableOffset = Size + TotalFolderSize;
2296
2297     /* Count number of files to be committed */
2298     CABHeader.FileCount = 0;
2299     FileNode = FileListHead;
2300     while (FileNode != NULL) {
2301         if (FileNode->Commit)
2302             CABHeader.FileCount++;
2303         FileNode = FileNode->Next;
2304     }
2305
2306     CABHeader.CabinetSize = DiskSize;
2307
2308     /* Write header */
2309     if (!WriteFile(FileHandle, &CABHeader, sizeof(CFHEADER), &BytesWritten, NULL)) {
2310         DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2311         return CAB_STATUS_CANNOT_WRITE;
2312     }
2313
2314     if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0) {
2315
2316         DPRINT(MAX_TRACE, ("CabinetPrev '%s'.\n", CabinetPrev));
2317
2318         /* Write name of previous cabinet */
2319         Size = lstrlen(CabinetPrev) + 1;
2320         if (!WriteFile(FileHandle, CabinetPrev, Size, &BytesWritten, NULL)) {
2321             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2322             return CAB_STATUS_CANNOT_WRITE;
2323         }
2324
2325         DPRINT(MAX_TRACE, ("DiskPrev '%s'.\n", DiskPrev));
2326
2327         /* Write label of previous disk */
2328         Size = lstrlen(DiskPrev) + 1;
2329         if (!WriteFile(FileHandle, DiskPrev, Size, &BytesWritten, NULL)) {
2330             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2331             return CAB_STATUS_CANNOT_WRITE;
2332         }
2333     }
2334
2335     if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0) {
2336
2337         DPRINT(MAX_TRACE, ("CabinetNext '%s'.\n", CabinetNext));
2338
2339         /* Write name of next cabinet */
2340         Size = lstrlen(CabinetNext) + 1;
2341         if (!WriteFile(FileHandle, CabinetNext, Size, &BytesWritten, NULL)) {
2342             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2343             return CAB_STATUS_CANNOT_WRITE;
2344         }
2345
2346         DPRINT(MAX_TRACE, ("DiskNext '%s'.\n", DiskNext));
2347
2348         /* Write label of next disk */
2349         Size = lstrlen(DiskNext) + 1;
2350         if (!WriteFile(FileHandle, DiskNext, Size, &BytesWritten, NULL)) {
2351             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2352             return CAB_STATUS_CANNOT_WRITE;
2353         }
2354     }
2355
2356     return CAB_STATUS_SUCCESS;
2357 }
2358
2359
2360 ULONG CCabinet::WriteFolderEntries()
2361 /*
2362  * FUNCTION: Writes folder entries
2363  * RETURNS:
2364  *     Status of operation
2365  */
2366 {
2367     PCFFOLDER_NODE FolderNode;
2368     DWORD BytesWritten;
2369
2370     DPRINT(MAX_TRACE, ("Writing folder table.\n"));
2371
2372     FolderNode = FolderListHead;
2373     while (FolderNode != NULL) {
2374         if (FolderNode->Commit) {
2375
2376             DPRINT(MAX_TRACE, ("Writing folder entry. CompressionType (0x%X)  DataBlockCount (%d)  DataOffset (0x%X).\n",
2377                 FolderNode->Folder.CompressionType, FolderNode->Folder.DataBlockCount, FolderNode->Folder.DataOffset));
2378
2379             if (!WriteFile(FileHandle,
2380                            &FolderNode->Folder,
2381                            sizeof(CFFOLDER),
2382                            &BytesWritten,
2383                            NULL)) {
2384                 DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2385                 return CAB_STATUS_CANNOT_WRITE;
2386             }
2387         }
2388         FolderNode = FolderNode->Next;
2389     }
2390
2391     return CAB_STATUS_SUCCESS;
2392 }
2393
2394
2395 ULONG CCabinet::WriteFileEntries()
2396 /*
2397  * FUNCTION: Writes file entries for all files
2398  * RETURNS:
2399  *     Status of operation
2400  */
2401 {
2402     PCFFILE_NODE File;
2403     DWORD BytesWritten;
2404     BOOL SetCont;
2405
2406     DPRINT(MAX_TRACE, ("Writing file table.\n"));
2407
2408     File = FileListHead;
2409     while (File != NULL) {
2410         if (File->Commit) {
2411             /* Remove any continued files that ends in this disk */
2412             if (File->File.FileControlID == CAB_FILE_CONTINUED)
2413                 File->Delete = TRUE;
2414
2415             /* The file could end in the last (split) block and should therefore
2416                appear in the next disk too */
2417             
2418             if ((File->File.FileOffset + File->File.FileSize >= LastBlockStart) &&
2419                 (File->File.FileControlID <= CAB_FILE_MAX_FOLDER) && (BlockIsSplit)) {
2420                 File->File.FileControlID = CAB_FILE_SPLIT;
2421                 File->Delete = FALSE;
2422                 SetCont = TRUE;
2423             }
2424
2425             DPRINT(MAX_TRACE, ("Writing file entry. FileControlID (0x%X)  FileOffset (0x%X)  FileSize (%d)  FileName (%s).\n",
2426                 File->File.FileControlID, File->File.FileOffset, File->File.FileSize, File->FileName));
2427
2428             if (!WriteFile(FileHandle,
2429                 &File->File,
2430                 sizeof(CFFILE),
2431                 &BytesWritten,
2432                 NULL))
2433                 return CAB_STATUS_CANNOT_WRITE;
2434
2435             if (!WriteFile(FileHandle,
2436                 GetFileName(File->FileName),
2437                 lstrlen(GetFileName(File->FileName)) + 1, &BytesWritten, NULL))
2438                 return CAB_STATUS_CANNOT_WRITE;
2439
2440             if (SetCont) {
2441                 File->File.FileControlID = CAB_FILE_CONTINUED;
2442                 SetCont = FALSE;
2443             }
2444         }
2445
2446         File = File->Next;
2447     }
2448     return CAB_STATUS_SUCCESS;
2449 }
2450
2451
2452 ULONG CCabinet::CommitDataBlocks(PCFFOLDER_NODE FolderNode)
2453 /*
2454  * FUNCTION: Writes data blocks to the cabinet
2455  * ARGUMENTS:
2456  *     FolderNode = Pointer to folder node containing the data blocks
2457  * RETURNS:
2458  *     Status of operation
2459  */
2460 {
2461     PCFDATA_NODE DataNode;
2462     DWORD BytesWritten;
2463     DWORD BytesRead;
2464     ULONG Status;
2465
2466     DataNode = FolderNode->DataListHead;
2467     if (DataNode != NULL)
2468         Status = ScratchFile->Seek(DataNode->ScratchFilePosition);
2469     
2470     while (DataNode != NULL) {
2471         DPRINT(MAX_TRACE, ("Reading block at (0x%X)  CompSize (%d)  UncompSize (%d).\n",
2472             DataNode->ScratchFilePosition,
2473             DataNode->Data.CompSize,
2474             DataNode->Data.UncompSize));
2475
2476         /* InputBuffer is free for us to use here, so we use it and avoid a
2477            memory allocation. OutputBuffer can't be used here because it may
2478            still contain valid data (if a data block spans two or more disks) */
2479         Status = ScratchFile->ReadBlock(&DataNode->Data, InputBuffer, &BytesRead);
2480         if (Status != CAB_STATUS_SUCCESS) {
2481             DPRINT(MIN_TRACE, ("Cannot read from scratch file (%d).\n", (UINT)Status));
2482             return Status;
2483         }
2484
2485         if (!WriteFile(FileHandle, &DataNode->Data,
2486             sizeof(CFDATA), &BytesWritten, NULL)) {
2487             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2488             return CAB_STATUS_CANNOT_WRITE;
2489         }
2490
2491         if (!WriteFile(FileHandle, InputBuffer,
2492             DataNode->Data.CompSize, &BytesWritten, NULL)) {
2493             DPRINT(MIN_TRACE, ("Cannot write to file.\n"));
2494             return CAB_STATUS_CANNOT_WRITE;
2495         }
2496
2497         DataNode = DataNode->Next;
2498     }
2499     return CAB_STATUS_SUCCESS;
2500 }
2501
2502
2503 ULONG CCabinet::WriteDataBlock()
2504 /*
2505  * FUNCTION: Writes the current data block to the scratch file
2506  * RETURNS:
2507  *     Status of operation
2508  */
2509 {
2510     ULONG Status;
2511     DWORD BytesWritten;
2512     PCFDATA_NODE DataNode;
2513
2514     if (!BlockIsSplit) {
2515         Status = Codec->Compress(OutputBuffer,
2516             InputBuffer,
2517             CurrentIBufferSize,
2518             &TotalCompSize);
2519
2520         DPRINT(MAX_TRACE, ("Block compressed. CurrentIBufferSize (%d)  TotalCompSize(%d).\n",
2521             CurrentIBufferSize, TotalCompSize));
2522
2523         CurrentOBuffer     = OutputBuffer;
2524         CurrentOBufferSize = TotalCompSize;
2525     }
2526
2527     DataNode = NewDataNode(CurrentFolderNode);
2528     if (!DataNode) {
2529         DPRINT(MIN_TRACE, ("Insufficient memory.\n"));
2530         return CAB_STATUS_NOMEMORY;
2531     }
2532
2533     DiskSize += sizeof(CFDATA);
2534
2535     if (MaxDiskSize > 0)
2536         /* Disk size is limited */
2537         BlockIsSplit = (DiskSize + CurrentOBufferSize > MaxDiskSize);
2538     else
2539         BlockIsSplit = FALSE;
2540
2541     if (BlockIsSplit) {
2542         DataNode->Data.CompSize   = (WORD)(MaxDiskSize - DiskSize);
2543         DataNode->Data.UncompSize = 0;
2544         CreateNewDisk = TRUE;
2545     } else {
2546         DataNode->Data.CompSize   = (WORD)CurrentOBufferSize;
2547         DataNode->Data.UncompSize = (WORD)CurrentIBufferSize;
2548     }
2549
2550     DataNode->Data.Checksum = 0;
2551     DataNode->ScratchFilePosition = ScratchFile->Position();
2552
2553     // FIXME: MAKECAB.EXE does not like this checksum algorithm
2554     //DataNode->Data.Checksum = ComputeChecksum(CurrentOBuffer, DataNode->Data.CompSize, 0);
2555
2556     DPRINT(MAX_TRACE, ("Writing block. Checksum (0x%X)  CompSize (%d)  UncompSize (%d).\n",
2557         (UINT)DataNode->Data.Checksum,
2558         (UINT)DataNode->Data.CompSize,
2559         (UINT)DataNode->Data.UncompSize));
2560
2561     Status = ScratchFile->WriteBlock(&DataNode->Data,
2562         CurrentOBuffer, &BytesWritten);
2563     if (Status != CAB_STATUS_SUCCESS)
2564         return Status;
2565
2566     DiskSize += BytesWritten;
2567
2568     CurrentFolderNode->TotalFolderSize += (BytesWritten + sizeof(CFDATA));
2569     CurrentFolderNode->Folder.DataBlockCount++;
2570
2571     (PUCHAR)CurrentOBuffer += DataNode->Data.CompSize;
2572     CurrentOBufferSize     -= DataNode->Data.CompSize;
2573
2574     LastBlockStart += DataNode->Data.UncompSize;
2575
2576     if (!BlockIsSplit) {
2577         CurrentIBufferSize = 0;
2578         CurrentIBuffer     = InputBuffer;
2579     }
2580
2581     return CAB_STATUS_SUCCESS;
2582 }
2583
2584
2585 ULONG CCabinet::GetAttributesOnFile(PCFFILE_NODE File)
2586 /*
2587  * FUNCTION: Returns attributes on a file
2588  * ARGUMENTS:
2589  *      File = Pointer to CFFILE node for file
2590  * RETURNS:
2591  *     Status of operation
2592  */
2593 {
2594     DWORD Attributes;
2595
2596     Attributes = GetFileAttributes(File->FileName);
2597     if (Attributes == -1)
2598         return CAB_STATUS_CANNOT_READ;
2599
2600     if (Attributes & FILE_ATTRIBUTE_READONLY)
2601         File->File.Attributes |= CAB_ATTRIB_READONLY;
2602
2603     if (Attributes & FILE_ATTRIBUTE_HIDDEN)
2604         File->File.Attributes |= CAB_ATTRIB_HIDDEN;
2605
2606     if (Attributes & FILE_ATTRIBUTE_SYSTEM)
2607         File->File.Attributes |= CAB_ATTRIB_SYSTEM;
2608
2609     if (Attributes & FILE_ATTRIBUTE_DIRECTORY)
2610         File->File.Attributes |= CAB_ATTRIB_DIRECTORY;
2611
2612     if (Attributes & FILE_ATTRIBUTE_ARCHIVE)
2613         File->File.Attributes |= CAB_ATTRIB_ARCHIVE;
2614
2615     return CAB_STATUS_SUCCESS;
2616 }
2617
2618
2619 ULONG CCabinet::SetAttributesOnFile(PCFFILE_NODE File)
2620 /*
2621  * FUNCTION: Sets attributes on a file
2622  * ARGUMENTS:
2623  *      File = Pointer to CFFILE node for file
2624  * RETURNS:
2625  *     Status of operation
2626  */
2627 {
2628     DWORD Attributes = 0;
2629
2630     if (File->File.Attributes & CAB_ATTRIB_READONLY)
2631         Attributes |= FILE_ATTRIBUTE_READONLY;
2632
2633     if (File->File.Attributes & CAB_ATTRIB_HIDDEN)
2634         Attributes |= FILE_ATTRIBUTE_HIDDEN;
2635
2636     if (File->File.Attributes & CAB_ATTRIB_SYSTEM)
2637         Attributes |= FILE_ATTRIBUTE_SYSTEM;
2638
2639     if (File->File.Attributes & CAB_ATTRIB_DIRECTORY)
2640         Attributes |= FILE_ATTRIBUTE_DIRECTORY;
2641
2642     if (File->File.Attributes & CAB_ATTRIB_ARCHIVE)
2643         Attributes |= FILE_ATTRIBUTE_ARCHIVE;
2644
2645     SetFileAttributes(File->FileName, Attributes);
2646
2647     return CAB_STATUS_SUCCESS;
2648 }
2649
2650 #endif /* CAB_READ_ONLY */
2651
2652 /* EOF */