update for HEAD-2003091401
[reactos.git] / subsys / system / usetup / cabinet.c
1 /*
2  * COPYRIGHT:   See COPYING in the top level directory
3  * PROJECT:     ReactOS text-mode setup
4  * FILE:        subsys/system/usetup/cabinet.c
5  * PURPOSE:     Cabinet routines
6  * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
7  * REVISIONS:
8  *   CSH 15/08-2003 Created
9  */
10 #include <ntos.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <zlib.h>
14 #include "cabinet.h"
15 #include "usetup.h"
16
17 #define NDEBUG
18 #include <debug.h>
19
20 #define SEEK_BEGIN    0
21 #define SEEK_CURRENT  1
22 #define SEEK_END      2
23
24 typedef struct __DOSTIME
25 {
26   WORD Second:5;
27   WORD Minute:6;
28   WORD Hour:5;
29 } DOSTIME, *PDOSTIME;
30
31
32 typedef struct __DOSDATE
33 {
34   WORD Day:5;
35   WORD Month:4;
36   WORD Year:5;
37 } DOSDATE, *PDOSDATE;
38
39 static WCHAR CabinetName[256];          // Filename of current cabinet
40 static WCHAR CabinetPrev[256];          // Filename of previous cabinet
41 static WCHAR DiskPrev[256];             // Label of cabinet in file CabinetPrev
42 static WCHAR CabinetNext[256];          // Filename of next cabinet
43 static WCHAR DiskNext[256];             // Label of cabinet in file CabinetNext
44 static ULONG FolderUncompSize = 0;      // Uncompressed size of folder
45 static ULONG BytesLeftInBlock = 0;      // Number of bytes left in current block
46 static BOOL ReuseBlock = FALSE;
47 static WCHAR DestPath[MAX_PATH];
48 static HANDLE FileHandle;
49 static BOOL FileOpen = FALSE;
50 static CFHEADER CABHeader;
51 static ULONG CabinetReserved = 0;
52 static ULONG FolderReserved = 0;
53 static ULONG DataReserved = 0;
54 static PCFFOLDER_NODE FolderListHead = NULL;
55 static PCFFOLDER_NODE FolderListTail = NULL;
56 static PCFFOLDER_NODE CurrentFolderNode = NULL;
57 static PCFDATA_NODE CurrentDataNode = NULL;
58 static PCFFILE_NODE FileListHead = NULL;
59 static PCFFILE_NODE FileListTail = NULL;
60 static ULONG CodecId;
61 static PCABINET_CODEC_UNCOMPRESS CodecUncompress = NULL;
62 static BOOL CodecSelected = FALSE;
63 static PVOID InputBuffer = NULL;
64 static PVOID CurrentIBuffer = NULL;       // Current offset in input buffer
65 static ULONG CurrentIBufferSize = 0;      // Bytes left in input buffer
66 static PVOID OutputBuffer = NULL;
67 static PVOID CurrentOBuffer = NULL;       // Current offset in output buffer
68 static ULONG CurrentOBufferSize = 0;      // Bytes left in output buffer
69 static BOOL RestartSearch = FALSE;
70 static ULONG LastFileOffset = 0;          // Uncompressed offset of last extracted file
71 static PCABINET_OVERWRITE OverwriteHandler = NULL;
72 static PCABINET_EXTRACT ExtractHandler = NULL;
73 static PCABINET_DISK_CHANGE DiskChangeHandler = NULL;
74 static z_stream ZStream;
75 static PVOID CabinetReservedArea = NULL;
76
77
78 /* Needed by zlib, but we don't want the dependency on msvcrt.dll */
79
80 void free(void* _ptr)
81 {
82   RtlFreeHeap(ProcessHeap, 0, _ptr);
83 }
84
85 void* calloc(size_t _nmemb, size_t _size)
86 {
87   return (void*)RtlAllocateHeap (ProcessHeap, HEAP_ZERO_MEMORY, _size);
88 }
89
90 /* RAW codec */
91
92 ULONG
93 RawCodecUncompress(PVOID OutputBuffer,
94   PVOID InputBuffer,
95   ULONG InputLength,
96   PULONG OutputLength)
97 /*
98  * FUNCTION: Uncompresses data in a buffer
99  * ARGUMENTS:
100  *     OutputBuffer = Pointer to buffer to place uncompressed data
101  *     InputBuffer  = Pointer to buffer with data to be uncompressed
102  *     InputLength  = Length of input buffer
103  *     OutputLength = Address of buffer to place size of uncompressed data
104  */
105 {
106   memcpy(OutputBuffer, InputBuffer, InputLength);
107   *OutputLength = InputLength;
108   return CS_SUCCESS;
109 }
110
111
112 /* MSZIP codec */
113
114 ULONG
115 MSZipCodecUncompress(PVOID OutputBuffer,
116   PVOID InputBuffer,
117   ULONG InputLength,
118   PULONG OutputLength)
119 /*
120  * FUNCTION: Uncompresses data in a buffer
121  * ARGUMENTS:
122  *     OutputBuffer = Pointer to buffer to place uncompressed data
123  *     InputBuffer  = Pointer to buffer with data to be uncompressed
124  *     InputLength  = Length of input buffer
125  *     OutputLength = Address of buffer to place size of uncompressed data
126  */
127 {
128   USHORT Magic;
129   INT Status;
130
131   DPRINT("InputLength (%d).\n", InputLength);
132
133   Magic = *((PUSHORT)InputBuffer);
134
135   if (Magic != MSZIP_MAGIC)
136     {
137       DPRINT("Bad MSZIP block header magic (0x%X)\n", Magic);
138       return CS_BADSTREAM;
139     }
140         
141         ZStream.next_in   = (PUCHAR)((ULONG)InputBuffer + 2);
142         ZStream.avail_in  = InputLength - 2;
143         ZStream.next_out  = (PUCHAR)OutputBuffer;
144   ZStream.avail_out = CAB_BLOCKSIZE + 12;
145
146   /* WindowBits is passed < 0 to tell that there is no zlib header.
147    * Note that in this case inflate *requires* an extra "dummy" byte
148    * after the compressed stream in order to complete decompression and
149    * return Z_STREAM_END.
150    */
151   Status = inflateInit2(&ZStream, -MAX_WBITS);
152   if (Status != Z_OK)
153     {
154       DPRINT("inflateInit2() returned (%d).\n", Status);
155       return CS_BADSTREAM;
156     }
157
158   while ((ZStream.total_out < CAB_BLOCKSIZE + 12) &&
159     (ZStream.total_in < InputLength - 2))
160     {
161       Status = inflate(&ZStream, Z_NO_FLUSH);
162       if (Status == Z_STREAM_END) break;
163       if (Status != Z_OK)
164         {
165           DPRINT("inflate() returned (%d) (%s).\n", Status, ZStream.msg);
166           if (Status == Z_MEM_ERROR)
167               return CS_NOMEMORY;
168           return CS_BADSTREAM;
169         }
170     }
171
172   *OutputLength = ZStream.total_out;
173
174   Status = inflateEnd(&ZStream);
175   if (Status != Z_OK)
176     {
177       DPRINT("inflateEnd() returned (%d).\n", Status);
178       return CS_BADSTREAM;
179     }
180   return CS_SUCCESS;
181 }
182
183
184
185 /* Memory functions */
186
187 voidpf MSZipAlloc(voidpf opaque, uInt items, uInt size)
188 {
189   return (voidpf)RtlAllocateHeap (ProcessHeap, 0, items * size);
190 }
191
192 void MSZipFree (voidpf opaque, voidpf address)
193 {
194   RtlFreeHeap(ProcessHeap, 0, address);
195 }
196
197
198 static DWORD
199 SeekInFile(HANDLE hFile,
200   LONG lDistanceToMove,
201   PLONG lpDistanceToMoveHigh,
202   DWORD dwMoveMethod,
203   PNTSTATUS Status)
204 {
205   FILE_POSITION_INFORMATION FilePosition;
206   FILE_STANDARD_INFORMATION FileStandart;
207   NTSTATUS errCode;
208   IO_STATUS_BLOCK IoStatusBlock;
209   LARGE_INTEGER Distance;
210   
211   DPRINT("SeekInFile(hFile %x, lDistanceToMove %d, dwMoveMethod %d)\n",
212     hFile,lDistanceToMove,dwMoveMethod);
213   
214   Distance.u.LowPart = lDistanceToMove;
215   if (lpDistanceToMoveHigh)
216     {
217       Distance.u.HighPart = *lpDistanceToMoveHigh;
218     }
219   else if (lDistanceToMove >= 0)
220     {
221       Distance.u.HighPart = 0;
222     }
223   else
224     {
225       Distance.u.HighPart = -1;
226     }
227   
228   if (dwMoveMethod == SEEK_CURRENT)
229     {
230       NtQueryInformationFile(hFile,
231         &IoStatusBlock,
232         &FilePosition,
233         sizeof(FILE_POSITION_INFORMATION),
234         FilePositionInformation);
235         FilePosition.CurrentByteOffset.QuadPart += Distance.QuadPart;
236     }
237   else if (dwMoveMethod == SEEK_END)
238     {
239       NtQueryInformationFile(hFile,
240         &IoStatusBlock,
241         &FileStandart,
242         sizeof(FILE_STANDARD_INFORMATION),
243         FileStandardInformation);
244         FilePosition.CurrentByteOffset.QuadPart =
245         FileStandart.EndOfFile.QuadPart + Distance.QuadPart;
246     }
247   else if ( dwMoveMethod == SEEK_BEGIN )
248     {
249       FilePosition.CurrentByteOffset.QuadPart = Distance.QuadPart;
250     }
251   
252 //  DPRINT1("GOTO FILE OFFSET: %I64d\n", FilePosition.CurrentByteOffset.QuadPart);
253
254   errCode = NtSetInformationFile(hFile,
255     &IoStatusBlock,
256     &FilePosition,
257     sizeof(FILE_POSITION_INFORMATION),
258     FilePositionInformation);
259   if (!NT_SUCCESS(errCode))
260     {
261       if (Status != NULL)
262         {
263           *Status = errCode;
264         }
265       return -1;
266     }
267   
268   if (lpDistanceToMoveHigh != NULL)
269     {
270       *lpDistanceToMoveHigh = FilePosition.CurrentByteOffset.u.HighPart;
271     }
272   if (Status != NULL)
273     {
274       *Status = STATUS_SUCCESS;
275     }
276   return FilePosition.CurrentByteOffset.u.LowPart;
277 }
278
279
280 static BOOL
281 ConvertSystemTimeToFileTime(
282   CONST SYSTEMTIME *  lpSystemTime,     
283   LPFILETIME  lpFileTime)
284 {
285   TIME_FIELDS TimeFields;
286   LARGE_INTEGER liTime;
287   
288   TimeFields.Year = lpSystemTime->wYear;
289   TimeFields.Month = lpSystemTime->wMonth;
290   TimeFields.Day = lpSystemTime->wDay;
291   TimeFields.Hour = lpSystemTime->wHour;
292   TimeFields.Minute = lpSystemTime->wMinute;
293   TimeFields.Second = lpSystemTime->wSecond;
294   TimeFields.Milliseconds = lpSystemTime->wMilliseconds;
295   
296   if (RtlTimeFieldsToTime(&TimeFields, &liTime))
297     {
298       lpFileTime->dwLowDateTime = liTime.u.LowPart;
299       lpFileTime->dwHighDateTime = liTime.u.HighPart;
300       return TRUE;
301     }
302   return FALSE;
303 }
304
305
306 static BOOL
307 ConvertDosDateTimeToFileTime(
308   WORD wFatDate,
309   WORD wFatTime,
310   LPFILETIME lpFileTime)
311 {
312   PDOSTIME  pdtime = (PDOSTIME) &wFatTime;
313   PDOSDATE  pddate = (PDOSDATE) &wFatDate;
314   SYSTEMTIME SystemTime;
315   
316   if (lpFileTime == NULL)
317     return FALSE;
318   
319   SystemTime.wMilliseconds = 0;
320   SystemTime.wSecond = pdtime->Second;
321   SystemTime.wMinute = pdtime->Minute;
322   SystemTime.wHour = pdtime->Hour;
323   
324   SystemTime.wDay = pddate->Day;
325   SystemTime.wMonth = pddate->Month;
326   SystemTime.wYear = 1980 + pddate->Year;
327   
328   ConvertSystemTimeToFileTime(&SystemTime,lpFileTime);
329   
330   return TRUE;
331 }
332
333
334 static PWCHAR
335 GetFileName(PWCHAR Path)
336 /*
337  * FUNCTION: Returns a pointer to file name
338  * ARGUMENTS:
339  *     Path = Pointer to string with pathname
340  * RETURNS:
341  *     Pointer to filename
342  */
343 {
344   ULONG i, j;
345   
346   j = i = 0;
347   
348   while (Path [i++])
349     {
350       if (Path[i - 1] == L'\\') j = i;
351     }
352   return Path + j;
353 }
354
355
356 static VOID
357 RemoveFileName(PWCHAR Path)
358 /*
359  * FUNCTION: Removes a file name from a path
360  * ARGUMENTS:
361  *     Path = Pointer to string with path
362  */
363 {
364   PWCHAR FileName;
365   DWORD i;
366   
367   i = 0;
368   FileName = GetFileName(Path + i);
369   
370   if ((FileName != (Path + i)) && (FileName [-1] == L'\\'))
371     FileName--;
372   if ((FileName == (Path + i)) && (FileName [0] == L'\\'))
373     FileName++;
374   FileName[0] = 0;
375 }
376
377
378 static BOOL
379 SetAttributesOnFile(PCFFILE_NODE File, HANDLE hFile)
380 /*
381  * FUNCTION: Sets attributes on a file
382  * ARGUMENTS:
383  *      File = Pointer to CFFILE node for file
384  * RETURNS:
385  *     Status of operation
386  */
387 {
388   FILE_BASIC_INFORMATION FileBasic;
389   IO_STATUS_BLOCK IoStatusBlock;
390   NTSTATUS NtStatus;
391   ULONG Attributes = 0;
392
393   if (File->File.Attributes & CAB_ATTRIB_READONLY)
394     Attributes |= FILE_ATTRIBUTE_READONLY;
395
396   if (File->File.Attributes & CAB_ATTRIB_HIDDEN)
397     Attributes |= FILE_ATTRIBUTE_HIDDEN;
398
399   if (File->File.Attributes & CAB_ATTRIB_SYSTEM)
400     Attributes |= FILE_ATTRIBUTE_SYSTEM;
401
402   if (File->File.Attributes & CAB_ATTRIB_DIRECTORY)
403     Attributes |= FILE_ATTRIBUTE_DIRECTORY;
404
405   if (File->File.Attributes & CAB_ATTRIB_ARCHIVE)
406     Attributes |= FILE_ATTRIBUTE_ARCHIVE;
407
408   NtStatus = NtQueryInformationFile(hFile,
409     &IoStatusBlock,
410     &FileBasic,
411     sizeof(FILE_BASIC_INFORMATION),
412     FileBasicInformation);
413   if (!NT_SUCCESS(NtStatus))
414     {
415       DPRINT("NtQueryInformationFile() failed (%x).\n", NtStatus);
416     }
417   else
418     {
419       FileBasic.FileAttributes = Attributes;
420
421       NtStatus = NtSetInformationFile(hFile,
422         &IoStatusBlock,
423         &FileBasic,
424         sizeof(FILE_BASIC_INFORMATION),
425         FileBasicInformation);
426       if (!NT_SUCCESS(NtStatus))
427         {
428           DPRINT("NtSetInformationFile() failed (%x).\n", NtStatus);
429         }
430     }
431
432   return NT_SUCCESS(NtStatus);
433 }
434
435
436 static ULONG
437 ReadBlock(PVOID Buffer,
438   ULONG Size,
439   PULONG BytesRead)
440 /*
441  * FUNCTION: Read a block of data from file
442  * ARGUMENTS:
443  *     Buffer    = Pointer to data buffer
444  *     Size      = Length of data buffer
445  *     BytesRead = Pointer to ULONG that on return will contain
446  *                 number of bytes read
447  * RETURNS:
448  *     Status of operation
449  */
450 {
451   IO_STATUS_BLOCK IoStatusBlock;
452   NTSTATUS NtStatus;
453
454   NtStatus = NtReadFile(FileHandle,
455           NULL,
456           NULL,
457           NULL,
458           &IoStatusBlock,
459           Buffer,
460           Size,
461           NULL,
462           NULL);
463   if (!NT_SUCCESS(NtStatus))
464           {
465       DPRINT("ReadBlock for %d bytes failed (%x)\n", Size, NtStatus);
466       *BytesRead = 0;
467       return CAB_STATUS_INVALID_CAB;
468     }
469   *BytesRead = Size;
470   return CAB_STATUS_SUCCESS;
471 }
472
473
474 static PCFFOLDER_NODE
475 NewFolderNode()
476 /*
477  * FUNCTION: Creates a new folder node
478  * RETURNS:
479  *     Pointer to node if there was enough free memory available, otherwise NULL
480  */
481 {
482   PCFFOLDER_NODE Node;
483
484   Node = (PCFFOLDER_NODE)RtlAllocateHeap (ProcessHeap, 0, sizeof(CFFOLDER_NODE));
485   if (!Node)
486     return NULL;
487   
488   RtlZeroMemory(Node, sizeof(CFFOLDER_NODE));
489   
490   Node->Folder.CompressionType = CAB_COMP_NONE;
491   
492   Node->Prev = FolderListTail;
493   
494   if (FolderListTail != NULL)
495     {
496       FolderListTail->Next = Node;
497     }
498   else
499     {
500       FolderListHead = Node;
501     }
502   FolderListTail = Node;
503   
504   return Node;
505 }
506
507
508 static PCFFILE_NODE
509 NewFileNode()
510 /*
511  * FUNCTION: Creates a new file node
512  * ARGUMENTS:
513  *     FolderNode = Pointer to folder node to bind file to
514  * RETURNS:
515  *     Pointer to node if there was enough free memory available, otherwise NULL
516  */
517 {
518   PCFFILE_NODE Node;
519
520   Node = (PCFFILE_NODE)RtlAllocateHeap (ProcessHeap, 0, sizeof(CFFILE_NODE));
521   if (!Node)
522     return NULL;
523
524   RtlZeroMemory(Node, sizeof(CFFILE_NODE));
525
526   Node->Prev = FileListTail;
527
528   if (FileListTail != NULL)
529     {
530       FileListTail->Next = Node;
531     }
532   else
533     {
534       FileListHead = Node;
535     }
536   FileListTail = Node;
537
538   return Node;
539 }
540
541
542 static PCFDATA_NODE
543 NewDataNode(PCFFOLDER_NODE FolderNode)
544 /*
545  * FUNCTION: Creates a new data block node
546  * ARGUMENTS:
547  *     FolderNode = Pointer to folder node to bind data block to
548  * RETURNS:
549  *     Pointer to node if there was enough free memory available, otherwise NULL
550  */
551 {
552   PCFDATA_NODE Node;
553
554   Node = (PCFDATA_NODE)RtlAllocateHeap (ProcessHeap, 0, sizeof(CFDATA_NODE));
555   if (!Node)
556     return NULL;
557
558   RtlZeroMemory(Node, sizeof(CFDATA_NODE));
559
560   Node->Prev = FolderNode->DataListTail;
561
562   if (FolderNode->DataListTail != NULL)
563     {
564       FolderNode->DataListTail->Next = Node;
565     }
566   else
567     {
568       FolderNode->DataListHead = Node;
569     }
570   FolderNode->DataListTail = Node;
571
572   return Node;
573 }
574
575
576 static VOID
577 DestroyDataNodes(PCFFOLDER_NODE FolderNode)
578 /*
579  * FUNCTION: Destroys data block nodes bound to a folder node
580  * ARGUMENTS:
581  *     FolderNode = Pointer to folder node
582  */
583 {
584   PCFDATA_NODE PrevNode;
585   PCFDATA_NODE NextNode;
586
587   NextNode = FolderNode->DataListHead;
588   while (NextNode != NULL)
589     {
590       PrevNode = NextNode->Next;
591       RtlFreeHeap(ProcessHeap, 0, NextNode);
592       NextNode = PrevNode;
593     }
594   FolderNode->DataListHead = NULL;
595   FolderNode->DataListTail = NULL;
596 }
597
598
599 static VOID
600 DestroyFileNodes()
601 /*
602  * FUNCTION: Destroys file nodes
603  * ARGUMENTS:
604  *     FolderNode = Pointer to folder node
605  */
606 {
607   PCFFILE_NODE PrevNode;
608   PCFFILE_NODE NextNode;
609
610   NextNode = FileListHead;
611   while (NextNode != NULL)
612     {
613       PrevNode = NextNode->Next;
614       if (NextNode->FileName)
615         RtlFreeHeap(ProcessHeap, 0, NextNode->FileName);
616       RtlFreeHeap(ProcessHeap, 0, NextNode);
617       NextNode = PrevNode;
618     }
619   FileListHead = NULL;
620   FileListTail = NULL;
621 }
622
623
624 static VOID
625 DestroyDeletedFileNodes()
626 /*
627  * FUNCTION: Destroys file nodes that are marked for deletion
628  */
629 {
630   PCFFILE_NODE CurNode;
631   PCFFILE_NODE NextNode;
632
633   CurNode = FileListHead;
634   while (CurNode != NULL)
635     {
636       NextNode = CurNode->Next;
637
638       if (CurNode->Delete)
639         {
640           if (CurNode->Prev != NULL)
641             {
642               CurNode->Prev->Next = CurNode->Next;
643             }
644           else
645             {
646               FileListHead = CurNode->Next;
647               if (FileListHead)
648                   FileListHead->Prev = NULL;
649             }
650
651           if (CurNode->Next != NULL)
652             {
653               CurNode->Next->Prev = CurNode->Prev;
654             }
655           else
656             {
657               FileListTail = CurNode->Prev;
658               if (FileListTail)
659                   FileListTail->Next = NULL;
660             }
661
662           DPRINT("Deleting file: '%S'\n", CurNode->FileName);
663
664           if (CurNode->FileName)
665             RtlFreeHeap(ProcessHeap, 0, CurNode->FileName);
666           RtlFreeHeap(ProcessHeap, 0, CurNode);
667         }
668       CurNode = NextNode;
669     }
670 }
671
672
673 static VOID
674 DestroyFolderNodes()
675 /*
676  * FUNCTION: Destroys folder nodes
677  */
678 {
679   PCFFOLDER_NODE PrevNode;
680   PCFFOLDER_NODE NextNode;
681
682   NextNode = FolderListHead;
683   while (NextNode != NULL)
684     {
685       PrevNode = NextNode->Next;
686       DestroyDataNodes(NextNode);
687       RtlFreeHeap(ProcessHeap, 0, NextNode);
688       NextNode = PrevNode;
689     }
690   FolderListHead = NULL;
691   FolderListTail = NULL;
692 }
693
694
695 static VOID
696 DestroyDeletedFolderNodes()
697 /*
698  * FUNCTION: Destroys folder nodes that are marked for deletion
699  */
700 {
701   PCFFOLDER_NODE CurNode;
702   PCFFOLDER_NODE NextNode;
703
704   CurNode = FolderListHead;
705   while (CurNode != NULL)
706     {
707       NextNode = CurNode->Next;
708
709       if (CurNode->Delete)
710         {
711           if (CurNode->Prev != NULL)
712             {
713               CurNode->Prev->Next = CurNode->Next;
714             }
715           else
716             {
717               FolderListHead = CurNode->Next;
718               if (FolderListHead)
719                 FolderListHead->Prev = NULL;
720             }
721
722           if (CurNode->Next != NULL)
723             {
724               CurNode->Next->Prev = CurNode->Prev;
725             }
726           else
727             {
728               FolderListTail = CurNode->Prev;
729               if (FolderListTail)
730                   FolderListTail->Next = NULL;
731             }
732
733           DestroyDataNodes(CurNode);
734           RtlFreeHeap(ProcessHeap, 0, CurNode);
735       }
736       CurNode = NextNode;
737     }
738 }
739
740
741 static PCFFOLDER_NODE
742 LocateFolderNode(ULONG Index)
743 /*
744  * FUNCTION: Locates a folder node
745  * ARGUMENTS:
746  *     Index = Folder index
747  * RETURNS:
748  *     Pointer to folder node or NULL if the folder node was not found
749  */
750 {
751   PCFFOLDER_NODE Node;
752   
753   switch (Index)
754     {
755       case CAB_FILE_SPLIT:
756         return FolderListTail;
757
758       case CAB_FILE_CONTINUED:
759       case CAB_FILE_PREV_NEXT:
760         return FolderListHead;
761     }
762   
763   Node = FolderListHead;
764   while (Node != NULL)
765     {
766       if (Node->Index == Index)
767         return Node;
768       Node = Node->Next;
769     }
770   return NULL;
771 }
772
773
774 static ULONG
775 GetAbsoluteOffset(PCFFILE_NODE File)
776 /*
777  * FUNCTION: Returns the absolute offset of a file
778  * ARGUMENTS:
779  *     File = Pointer to CFFILE_NODE structure for file
780  * RETURNS:
781  *     Status of operation
782  */
783 {
784   PCFDATA_NODE Node;
785
786   DPRINT("FileName '%S'  FileOffset (0x%X)  FileSize (%d).\n",
787     (PWCHAR)File->FileName,
788     (UINT)File->File.FileOffset,
789     (UINT)File->File.FileSize);
790
791   Node = CurrentFolderNode->DataListHead;
792   while (Node != NULL)
793     {
794       DPRINT("GetAbsoluteOffset(): Comparing (0x%X, 0x%X) (%d).\n",
795           (UINT)Node->UncompOffset,
796           (UINT)Node->UncompOffset + Node->Data.UncompSize,
797           (UINT)Node->Data.UncompSize);
798
799       /* Node->Data.UncompSize will be 0 if the block is split
800          (ie. it is the last block in this cabinet) */
801       if ((Node->Data.UncompSize == 0) ||
802         ((File->File.FileOffset >= Node->UncompOffset) &&
803         (File->File.FileOffset < Node->UncompOffset +
804         Node->Data.UncompSize)))
805         {
806           File->DataBlock = Node;
807           return CAB_STATUS_SUCCESS;
808         }
809
810       Node = Node->Next;
811     }
812   return CAB_STATUS_INVALID_CAB;
813 }
814
815
816 static ULONG
817 LocateFile(PWCHAR FileName,
818   PCFFILE_NODE *File)
819 /*
820  * FUNCTION: Locates a file in the cabinet
821  * ARGUMENTS:
822  *     FileName = Pointer to string with name of file to locate
823  *     File     = Address of pointer to CFFILE_NODE structure to fill
824  * RETURNS:
825  *     Status of operation
826  * NOTES:
827  *     Current folder is set to the folder of the file
828  */
829 {
830   PCFFILE_NODE Node;
831   ULONG Status;
832
833   DPRINT("FileName '%S'\n", FileName);
834
835   Node = FileListHead;
836   while (Node != NULL)
837     {
838       if (_wcsicmp(FileName, Node->FileName) == 0)
839         {
840           CurrentFolderNode = LocateFolderNode(Node->File.FileControlID);
841           if (!CurrentFolderNode)
842             {
843               DPRINT("Folder with index number (%d) not found.\n",
844                 (UINT)Node->File.FileControlID);
845               return CAB_STATUS_INVALID_CAB;
846             }
847
848           if (Node->DataBlock == NULL)
849             {
850               Status = GetAbsoluteOffset(Node);
851             }
852           else
853             Status = CAB_STATUS_SUCCESS;
854           *File = Node;
855           return Status;
856       }
857       Node = Node->Next;
858   }
859   return CAB_STATUS_NOFILE;
860 }
861
862
863 static ULONG
864 ReadString(PWCHAR String, ULONG MaxLength)
865 /*
866  * FUNCTION: Reads a NULL-terminated string from the cabinet
867  * ARGUMENTS:
868  *     String    = Pointer to buffer to place string
869  *     MaxLength = Maximum length of string
870  * RETURNS:
871  *     Status of operation
872  */
873 {
874   NTSTATUS NtStatus;
875   ULONG BytesRead;
876   ULONG Offset;
877   ULONG Status;
878   ULONG Size;
879   BOOL Found;
880   CHAR buf[MAX_PATH];
881   ANSI_STRING as;
882   UNICODE_STRING us;
883
884   Offset = 0;
885   Found  = FALSE;
886   do
887     {
888       Size = ((Offset + 32) <= MaxLength)? 32 : MaxLength - Offset;
889
890       if (Size == 0)
891         {
892           DPRINT("Too long a filename.\n");
893           return CAB_STATUS_INVALID_CAB;
894         }
895
896       Status = ReadBlock((PCFDATA)&buf[Offset], Size, &BytesRead);
897       if ((Status != CAB_STATUS_SUCCESS) || (BytesRead != Size))
898         {
899           DPRINT("Cannot read from file (%d).\n", (UINT)Status);
900           return CAB_STATUS_INVALID_CAB;
901         }
902
903       for (Size = Offset; Size < Offset + BytesRead; Size++)
904         {
905           if (buf[Size] == '\0')
906             {
907               Found = TRUE;
908               break;
909             }
910         }
911
912       Offset += BytesRead;
913     } while (!Found);
914
915   /* Back up some bytes */
916   Size = (BytesRead - Size) - 1;
917   SeekInFile(FileHandle, -(LONG)Size, NULL, SEEK_CURRENT, &NtStatus);
918   if (!NT_SUCCESS(NtStatus))
919     {
920       DPRINT("SeekInFile() failed (%x).\n", NtStatus);
921       return CAB_STATUS_INVALID_CAB;
922     }
923
924   RtlInitAnsiString(&as, buf);
925   us.Buffer = String;
926   us.MaximumLength = MaxLength * sizeof(WCHAR);
927   us.Length = 0;
928
929   RtlAnsiStringToUnicodeString(&us, &as, FALSE);
930
931   return CAB_STATUS_SUCCESS;
932 }
933
934
935 static ULONG
936 ReadFileTable()
937 /*
938  * FUNCTION: Reads the file table from the cabinet file
939  * RETURNS:
940  *     Status of operation
941  */
942 {
943   ULONG i;
944   ULONG Status;
945   ULONG BytesRead;
946   PCFFILE_NODE File;
947   NTSTATUS NtStatus;
948
949   DPRINT("Reading file table at absolute offset (0x%X).\n",
950     CABHeader.FileTableOffset);
951
952   /* Seek to file table */
953   SeekInFile(FileHandle, CABHeader.FileTableOffset, NULL, SEEK_BEGIN, &NtStatus);
954   if (!NT_SUCCESS(NtStatus))
955     {
956       DPRINT("SeekInFile() failed (%x).\n", NtStatus);
957       return CAB_STATUS_INVALID_CAB;
958     }
959
960   for (i = 0; i < CABHeader.FileCount; i++)
961     {
962       File = NewFileNode();
963       if (!File)
964         {
965           DPRINT("Insufficient memory.\n");
966           return CAB_STATUS_NOMEMORY;
967         }
968
969       if ((Status = ReadBlock(&File->File, sizeof(CFFILE),
970           &BytesRead)) != CAB_STATUS_SUCCESS) {
971           DPRINT("Cannot read from file (%d).\n", (UINT)Status);
972           return CAB_STATUS_INVALID_CAB;
973       }
974
975       File->FileName = (PWCHAR)RtlAllocateHeap(ProcessHeap, 0, MAX_PATH * sizeof(WCHAR));
976       if (!File->FileName)
977         {
978           DPRINT("Insufficient memory.\n");
979           return CAB_STATUS_NOMEMORY;
980         }
981
982       /* Read file name */
983       Status = ReadString(File->FileName, MAX_PATH);
984       if (Status != CAB_STATUS_SUCCESS)
985         return Status;
986
987       DPRINT("Found file '%S' at uncompressed offset (0x%X).  Size (%d bytes)  ControlId (0x%X).\n",
988         (PWCHAR)File->FileName,
989         (UINT)File->File.FileOffset,
990         (UINT)File->File.FileSize,
991         (UINT)File->File.FileControlID);
992     }
993   return CAB_STATUS_SUCCESS;
994 }
995
996
997 static ULONG
998 ReadDataBlocks(PCFFOLDER_NODE FolderNode)
999 /*
1000  * FUNCTION: Reads all CFDATA blocks for a folder from the cabinet file
1001  * ARGUMENTS:
1002  *     FolderNode = Pointer to CFFOLDER_NODE structure for folder
1003  * RETURNS:
1004  *     Status of operation
1005  */
1006 {
1007   ULONG AbsoluteOffset;
1008   ULONG UncompOffset;
1009   PCFDATA_NODE Node;
1010   NTSTATUS NtStatus;
1011   ULONG BytesRead;
1012   ULONG Status;
1013   ULONG i;
1014
1015   DPRINT("Reading data blocks for folder (%d)  at absolute offset (0x%X).\n",
1016     FolderNode->Index, FolderNode->Folder.DataOffset);
1017
1018   AbsoluteOffset = FolderNode->Folder.DataOffset;
1019   UncompOffset = FolderNode->UncompOffset;
1020
1021   for (i = 0; i < FolderNode->Folder.DataBlockCount; i++)
1022     {
1023       Node = NewDataNode(FolderNode);
1024       if (!Node)
1025         {
1026           DPRINT("Insufficient memory.\n");
1027           return CAB_STATUS_NOMEMORY;
1028         }
1029
1030       /* Seek to data block */
1031       SeekInFile(FileHandle, AbsoluteOffset, NULL, SEEK_BEGIN, &NtStatus);
1032       if (!NT_SUCCESS(NtStatus))
1033         {
1034           DPRINT("SeekInFile() failed (%x).\n", NtStatus);
1035           return CAB_STATUS_INVALID_CAB;
1036         }
1037
1038       if ((Status = ReadBlock(&Node->Data, sizeof(CFDATA),
1039           &BytesRead)) != CAB_STATUS_SUCCESS)
1040         {
1041           DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1042           return CAB_STATUS_INVALID_CAB;
1043         }
1044
1045       DPRINT("AbsOffset (0x%X)  UncompOffset (0x%X)  Checksum (0x%X)  CompSize (%d)  UncompSize (%d).\n",
1046         (UINT)AbsoluteOffset,
1047         (UINT)UncompOffset,
1048         (UINT)Node->Data.Checksum,
1049         (UINT)Node->Data.CompSize,
1050         (UINT)Node->Data.UncompSize);
1051
1052       Node->AbsoluteOffset = AbsoluteOffset;
1053       Node->UncompOffset = UncompOffset;
1054
1055       AbsoluteOffset += sizeof(CFDATA) + Node->Data.CompSize;
1056       UncompOffset += Node->Data.UncompSize;
1057     }
1058
1059   FolderUncompSize = UncompOffset;
1060
1061   return CAB_STATUS_SUCCESS;
1062 }
1063
1064
1065 static ULONG
1066 ComputeChecksum(PVOID Buffer,
1067   UINT Size,
1068   ULONG Seed)
1069 /*
1070  * FUNCTION: Computes checksum for data block
1071  * ARGUMENTS:
1072  *     Buffer = Pointer to data buffer
1073  *     Size   = Length of data buffer
1074  *     Seed   = Previously computed checksum
1075  * RETURNS:
1076  *     Checksum of buffer
1077  */
1078 {
1079   INT UlongCount; // Number of ULONGs in block
1080   ULONG Checksum; // Checksum accumulator
1081   PBYTE pb;
1082   ULONG ul;
1083
1084   /* FIXME: Doesn't seem to be correct. EXTRACT.EXE
1085      won't accept checksums computed by this routine */
1086
1087   DPRINT("Checksumming buffer (0x%X)  Size (%d)\n", (UINT)Buffer, Size);
1088
1089   UlongCount = Size / 4;            // Number of ULONGs
1090   Checksum = Seed;                  // Init checksum
1091   pb = (PBYTE)Buffer;               // Start at front of data block
1092
1093   /* Checksum integral multiple of ULONGs */
1094   while (UlongCount-- > 0)
1095     {
1096       /* NOTE: Build ULONG in big/little-endian independent manner */
1097       ul = *pb++;                     // Get low-order byte
1098       ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
1099       ul |= (((ULONG)(*pb++)) << 16); // Add 3nd byte
1100       ul |= (((ULONG)(*pb++)) << 24); // Add 4th byte
1101
1102       Checksum ^= ul;                 // Update checksum
1103   }
1104
1105   /* Checksum remainder bytes */
1106   ul = 0;
1107   switch (Size % 4)
1108     {
1109       case 3:
1110           ul |= (((ULONG)(*pb++)) << 16); // Add 3rd byte
1111       case 2:
1112           ul |= (((ULONG)(*pb++)) <<  8); // Add 2nd byte
1113       case 1:
1114           ul |= *pb++;                    // Get low-order byte
1115       default:
1116           break;
1117     }
1118   Checksum ^= ul;                         // Update checksum
1119
1120   /* Return computed checksum */
1121   return Checksum;
1122 }
1123
1124
1125 static ULONG
1126 CloseCabinet()
1127 /*
1128  * FUNCTION: Closes the current cabinet
1129  * RETURNS:
1130  *     Status of operation
1131  */
1132 {
1133   PCFFOLDER_NODE PrevNode;
1134   PCFFOLDER_NODE NextNode;
1135   ULONG Status;
1136   
1137   DestroyFileNodes();
1138   
1139   DestroyFolderNodes();
1140   
1141   if (InputBuffer)
1142     {
1143       RtlFreeHeap(ProcessHeap, 0, InputBuffer);
1144       InputBuffer = NULL;
1145     }
1146   
1147   if (OutputBuffer)
1148     {
1149       RtlFreeHeap(ProcessHeap, 0, OutputBuffer);
1150       OutputBuffer = NULL;
1151     }
1152
1153   NtClose(FileHandle);
1154 }
1155
1156
1157 VOID
1158 CabinetInitialize()
1159 /*
1160  * FUNCTION: Initialize archiver
1161  */
1162 {
1163   ZStream.zalloc = MSZipAlloc;
1164   ZStream.zfree  = MSZipFree;
1165   ZStream.opaque = (voidpf)0;
1166
1167   FileOpen = FALSE;
1168   wcscpy(DestPath, L"");
1169   
1170   FolderListHead = NULL;
1171   FolderListTail = NULL;
1172   FileListHead   = NULL;
1173   FileListTail   = NULL;
1174   
1175   CodecId       = CAB_CODEC_RAW;
1176   CodecSelected = TRUE;
1177   
1178   OutputBuffer = NULL;
1179   CurrentOBuffer = NULL;
1180   CurrentOBufferSize = 0;
1181   InputBuffer  = NULL;
1182   CurrentIBuffer = NULL;
1183   CurrentIBufferSize = 0;
1184
1185   FolderUncompSize = 0;
1186   BytesLeftInBlock = 0;
1187   CabinetReserved = 0;
1188   FolderReserved = 0;
1189   DataReserved = 0;
1190   ReuseBlock       = FALSE;
1191   CurrentFolderNode = NULL;
1192   CurrentDataNode  = NULL;
1193   CabinetReservedArea = NULL;
1194   RestartSearch = FALSE;
1195   LastFileOffset = 0;
1196 }
1197
1198
1199 VOID
1200 CabinetCleanup()
1201 /*
1202  * FUNCTION: Cleanup archiver
1203  */
1204 {
1205   CabinetClose();
1206 }
1207
1208
1209 BOOL
1210 CabinetNormalizePath(PWCHAR Path,
1211   ULONG Length)
1212 /*
1213  * FUNCTION: Normalizes a path
1214  * ARGUMENTS:
1215  *     Path   = Pointer to string with pathname
1216  *     Length = Number of characters in Path
1217  * RETURNS:
1218  *     TRUE if there was enough room in Path, or FALSE
1219  */
1220 {
1221   ULONG n;
1222   BOOL OK = TRUE;
1223   
1224   if ((n = wcslen(Path)) &&
1225     (Path[n - 1] != L'\\') &&
1226     (OK = ((n + 1) < Length)))
1227     {
1228       Path[n]     = L'\\';
1229       Path[n + 1] = 0;
1230     }
1231   return OK;
1232 }
1233
1234
1235 PWCHAR
1236 CabinetGetCabinetName()
1237 /*
1238  * FUNCTION: Returns pointer to cabinet file name
1239  * RETURNS:
1240  *     Pointer to string with name of cabinet
1241  */
1242 {
1243   return CabinetName;
1244 }
1245
1246
1247 VOID
1248 CabinetSetCabinetName(PWCHAR FileName)
1249 /*
1250  * FUNCTION: Sets cabinet file name
1251  * ARGUMENTS:
1252  *     FileName = Pointer to string with name of cabinet
1253  */
1254 {
1255   wcscpy(CabinetName, FileName);
1256 }
1257
1258
1259 VOID
1260 CabinetSetDestinationPath(PWCHAR DestinationPath)
1261 /*
1262  * FUNCTION: Sets destination path
1263  * ARGUMENTS:
1264  *    DestinationPath = Pointer to string with name of destination path
1265  */
1266 {
1267   wcscpy(DestPath, DestinationPath);
1268   if (wcslen(DestPath) > 0)
1269     CabinetNormalizePath(DestPath, MAX_PATH);
1270 }
1271
1272
1273 PWCHAR
1274 CabinetGetDestinationPath()
1275 /*
1276  * FUNCTION: Returns destination path
1277  * RETURNS:
1278  *    Pointer to string with name of destination path
1279  */
1280 {
1281   return DestPath;
1282 }
1283
1284
1285 ULONG
1286 CabinetOpen()
1287 /*
1288  * FUNCTION: Opens a cabinet file
1289  * RETURNS:
1290  *     Status of operation
1291  */
1292 {
1293   WCHAR CabinetFileName[256];
1294   PCFFOLDER_NODE FolderNode;
1295   ULONG Status;
1296   ULONG Index;
1297   
1298   if (!FileOpen)
1299     {
1300       OBJECT_ATTRIBUTES ObjectAttributes;
1301       IO_STATUS_BLOCK IoStatusBlock;
1302       UNICODE_STRING FileName;
1303       NTSTATUS NtStatus;
1304       ULONG BytesRead;
1305       ULONG Size;
1306     
1307       OutputBuffer = RtlAllocateHeap(ProcessHeap, 0, CAB_BLOCKSIZE + 12); // This should be enough
1308       if (!OutputBuffer)
1309         return CAB_STATUS_NOMEMORY;
1310
1311       RtlInitUnicodeString(&FileName,
1312         CabinetName);
1313
1314       InitializeObjectAttributes(&ObjectAttributes,
1315         &FileName,
1316         OBJ_CASE_INSENSITIVE,
1317         NULL,
1318         NULL);
1319
1320       NtStatus = NtOpenFile(&FileHandle,
1321         FILE_READ_ACCESS,
1322                     &ObjectAttributes,
1323                     &IoStatusBlock,
1324                     FILE_SHARE_READ,
1325                     FILE_SYNCHRONOUS_IO_ALERT);
1326       if (!NT_SUCCESS(NtStatus))
1327         {
1328           DPRINT("Cannot open file (%S) (%x).\n", CabinetName, NtStatus);
1329           return CAB_STATUS_CANNOT_OPEN;
1330         }
1331     
1332       FileOpen = TRUE;
1333     
1334       /* Load CAB header */
1335       if ((Status = ReadBlock(&CABHeader, sizeof(CFHEADER), &BytesRead)) != CAB_STATUS_SUCCESS)
1336         {
1337           DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1338           return CAB_STATUS_INVALID_CAB;
1339         }
1340     
1341       /* Check header */
1342       if ((BytesRead               != sizeof(CFHEADER)) ||
1343         (CABHeader.Signature       != CAB_SIGNATURE   ) ||
1344         (CABHeader.Version         != CAB_VERSION     ) ||
1345         (CABHeader.FolderCount     == 0               ) ||
1346         (CABHeader.FileCount       == 0               ) ||
1347         (CABHeader.FileTableOffset < sizeof(CFHEADER))) {
1348         CloseCabinet();
1349         DPRINT("File has invalid header.\n");
1350         return CAB_STATUS_INVALID_CAB;
1351       }
1352   
1353       Size = 0;
1354   
1355       /* Read/skip any reserved bytes */
1356       if (CABHeader.Flags & CAB_FLAG_RESERVE)
1357         {
1358           if ((Status = ReadBlock(&Size, sizeof(ULONG), &BytesRead)) != CAB_STATUS_SUCCESS)
1359             {
1360               DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1361               return CAB_STATUS_INVALID_CAB;
1362             }
1363           CabinetReserved = Size & 0xFFFF;
1364           FolderReserved = (Size >> 16) & 0xFF;
1365           DataReserved = (Size >> 24) & 0xFF;
1366
1367           if (CabinetReserved > 0)
1368             {
1369               CabinetReservedArea = RtlAllocateHeap(ProcessHeap, 0, CabinetReserved);
1370               if (!CabinetReservedArea)
1371                 {
1372                   return CAB_STATUS_NOMEMORY;
1373                 }
1374
1375               if ((Status = ReadBlock(CabinetReservedArea, CabinetReserved, &BytesRead)) != CAB_STATUS_SUCCESS)
1376                 {
1377                   DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1378                   return CAB_STATUS_INVALID_CAB;
1379                 }
1380             }
1381 #if 0
1382           SeekInFile(FileHandle, CabinetReserved, NULL, SEEK_CURRENT, &NtStatus);
1383           if (!NT_SUCCESS(NtStatus))
1384             {
1385               DPRINT("SeekInFile() failed (%x).\n", NtStatus);
1386               return CAB_STATUS_INVALID_CAB;
1387             }
1388 #endif
1389         }
1390   
1391       if ((CABHeader.Flags & CAB_FLAG_HASPREV) > 0)
1392         {
1393           /* Read name of previous cabinet */
1394           Status = ReadString(CabinetFileName, 256);
1395           if (Status != CAB_STATUS_SUCCESS)
1396             return Status;
1397
1398           /* The previous cabinet file is in the same directory as the current */
1399           wcscpy(CabinetPrev, CabinetName);
1400           RemoveFileName(CabinetPrev);
1401           CabinetNormalizePath(CabinetPrev, 256);
1402           wcscat(CabinetPrev, CabinetFileName);
1403
1404           /* Read label of previous disk */
1405           Status = ReadString(DiskPrev, 256);
1406           if (Status != CAB_STATUS_SUCCESS)
1407             return Status;
1408         }
1409       else
1410         {
1411           wcscpy(CabinetPrev, L"");
1412           wcscpy(DiskPrev, L"");
1413         }
1414     
1415       if ((CABHeader.Flags & CAB_FLAG_HASNEXT) > 0)
1416         {
1417           /* Read name of next cabinet */
1418           Status = ReadString(CabinetFileName, 256);
1419           if (Status != CAB_STATUS_SUCCESS)
1420               return Status;
1421
1422           /* The next cabinet file is in the same directory as the previous */
1423           wcscpy(CabinetNext, CabinetName);
1424           RemoveFileName(CabinetNext);
1425           CabinetNormalizePath(CabinetNext, 256);
1426           wcscat(CabinetNext, CabinetFileName);
1427
1428           /* Read label of next disk */
1429           Status = ReadString(DiskNext, 256);
1430           if (Status != CAB_STATUS_SUCCESS)
1431               return Status;
1432         }
1433       else
1434         {
1435           wcscpy(CabinetNext, L"");
1436           wcscpy(DiskNext,    L"");
1437         }
1438   
1439       /* Read all folders */
1440       for (Index = 0; Index < CABHeader.FolderCount; Index++)
1441         {
1442           FolderNode = NewFolderNode();
1443           if (!FolderNode)
1444             {
1445               DPRINT("Insufficient resources.\n");
1446               return CAB_STATUS_NOMEMORY;
1447             }
1448   
1449           if (Index == 0)
1450             FolderNode->UncompOffset = FolderUncompSize;
1451     
1452           FolderNode->Index = Index;
1453   
1454           if ((Status = ReadBlock(&FolderNode->Folder,
1455             sizeof(CFFOLDER), &BytesRead)) != CAB_STATUS_SUCCESS)
1456             {
1457               DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1458               return CAB_STATUS_INVALID_CAB;
1459             }
1460         }
1461   
1462       /* Read file entries */
1463       Status = ReadFileTable();
1464       if (Status != CAB_STATUS_SUCCESS)
1465         {
1466           DPRINT("ReadFileTable() failed (%d).\n", (UINT)Status);
1467           return Status;
1468         }
1469   
1470       /* Read data blocks for all folders */
1471       FolderNode = FolderListHead;
1472       while (FolderNode != NULL)
1473         {
1474           Status = ReadDataBlocks(FolderNode);
1475           if (Status != CAB_STATUS_SUCCESS)
1476             {
1477               DPRINT("ReadDataBlocks() failed (%d).\n", (UINT)Status);
1478               return Status;
1479             }
1480           FolderNode = FolderNode->Next;
1481         }
1482     }
1483   return CAB_STATUS_SUCCESS;
1484 }
1485
1486
1487 VOID
1488 CabinetClose()
1489 /*
1490  * FUNCTION: Closes the cabinet file
1491  */
1492 {
1493   if (FileOpen)
1494     {
1495       CloseCabinet();
1496
1497       if (CabinetReservedArea != NULL)
1498         {
1499           RtlFreeHeap(ProcessHeap, 0, CabinetReservedArea);
1500           CabinetReservedArea = NULL;
1501         }
1502
1503       FileOpen = FALSE;
1504     }
1505 }
1506
1507
1508 ULONG
1509 CabinetFindFirst(PWCHAR FileName,
1510   PCAB_SEARCH Search)
1511 /*
1512  * FUNCTION: Finds the first file in the cabinet that matches a search criteria
1513  * ARGUMENTS:
1514  *     FileName = Pointer to search criteria
1515  *     Search   = Pointer to search structure
1516  * RETURNS:
1517  *     Status of operation
1518  */
1519 {
1520   RestartSearch = FALSE;
1521   wcsncpy(Search->Search, FileName, MAX_PATH);
1522   Search->Next = FileListHead;
1523   return CabinetFindNext(Search);
1524 }
1525
1526
1527 ULONG
1528 CabinetFindNext(PCAB_SEARCH Search)
1529 /*
1530  * FUNCTION: Finds next file in the cabinet that matches a search criteria
1531  * ARGUMENTS:
1532  *     Search = Pointer to search structure
1533  * RETURNS:
1534  *     Status of operation
1535  */
1536 {
1537   ULONG Status;
1538
1539   if (RestartSearch)
1540     {
1541       Search->Next = FileListHead;
1542
1543       /* Skip split files already extracted */
1544       while ((Search->Next) &&
1545         (Search->Next->File.FileControlID > CAB_FILE_MAX_FOLDER) &&
1546         (Search->Next->File.FileOffset <= LastFileOffset))
1547         {
1548           DPRINT("Skipping file (%s)  FileOffset (0x%X)  LastFileOffset (0x%X).\n",
1549             Search->Next->FileName, Search->Next->File.FileOffset, LastFileOffset);
1550           Search->Next = Search->Next->Next;
1551         }
1552
1553       RestartSearch = FALSE;
1554     }
1555
1556   /* FIXME: Check search criteria */
1557
1558   if (!Search->Next)
1559     {
1560       if (wcslen(DiskNext) > 0)
1561         {
1562           CloseCabinet();
1563
1564           CabinetSetCabinetName(CabinetNext);
1565
1566           if (DiskChangeHandler != NULL)
1567             {
1568               DiskChangeHandler(CabinetNext, DiskNext);
1569             }
1570
1571           Status = CabinetOpen();
1572           if (Status != CAB_STATUS_SUCCESS) 
1573             return Status;
1574
1575           Search->Next = FileListHead;
1576           if (!Search->Next)
1577             return CAB_STATUS_NOFILE;
1578         }
1579       else
1580         {
1581           return CAB_STATUS_NOFILE;
1582         }
1583   }
1584
1585   Search->File = &Search->Next->File;
1586   Search->FileName = Search->Next->FileName;
1587   Search->Next = Search->Next->Next;
1588   return CAB_STATUS_SUCCESS;
1589 }
1590
1591
1592 ULONG
1593 CabinetExtractFile(PWCHAR FileName)
1594 /*
1595  * FUNCTION: Extracts a file from the cabinet
1596  * ARGUMENTS:
1597  *     FileName = Pointer to buffer with name of file
1598  * RETURNS
1599  *     Status of operation
1600  */
1601 {
1602   ULONG Size;
1603   ULONG Offset;
1604   ULONG BytesRead;
1605   ULONG BytesToRead;
1606   ULONG BytesWritten;
1607   ULONG BytesSkipped;
1608   ULONG BytesToWrite;
1609   ULONG TotalBytesRead;
1610   ULONG CurrentOffset;
1611   PUCHAR Buffer;
1612   PUCHAR CurrentBuffer;
1613   HANDLE DestFile;
1614   PCFFILE_NODE File;
1615   CFDATA CFData;
1616   ULONG Status;
1617   BOOL Skip;
1618   FILETIME FileTime;
1619   WCHAR DestName[MAX_PATH];
1620   WCHAR TempName[MAX_PATH];
1621   NTSTATUS NtStatus;
1622   UNICODE_STRING UnicodeString;
1623   IO_STATUS_BLOCK IoStatusBlock;
1624   OBJECT_ATTRIBUTES ObjectAttributes;
1625   FILE_BASIC_INFORMATION FileBasic;
1626
1627   Status = LocateFile(FileName, &File);
1628   if (Status != CAB_STATUS_SUCCESS)
1629     {
1630       DPRINT("Cannot locate file (%d).\n", (UINT)Status);
1631       return Status;
1632     }
1633
1634   LastFileOffset = File->File.FileOffset;
1635   
1636   switch (CurrentFolderNode->Folder.CompressionType & CAB_COMP_MASK)
1637     {
1638       case CAB_COMP_NONE:
1639         CabinetSelectCodec(CAB_CODEC_RAW);
1640         break;
1641       case CAB_COMP_MSZIP:
1642         CabinetSelectCodec(CAB_CODEC_MSZIP);
1643         break;
1644       default:
1645         return CAB_STATUS_UNSUPPCOMP;
1646     }
1647   
1648   DPRINT("Extracting file at uncompressed offset (0x%X)  Size (%d bytes)  AO (0x%X)  UO (0x%X).\n",
1649     (UINT)File->File.FileOffset,
1650     (UINT)File->File.FileSize,
1651     (UINT)File->DataBlock->AbsoluteOffset,
1652     (UINT)File->DataBlock->UncompOffset);
1653
1654   wcscpy(DestName, DestPath);
1655   wcscat(DestName, FileName);
1656   
1657   /* Create destination file, fail if it already exists */
1658   RtlInitUnicodeString(&UnicodeString,
1659     DestName);
1660
1661   InitializeObjectAttributes(&ObjectAttributes,
1662     &UnicodeString,
1663     OBJ_CASE_INSENSITIVE,
1664     NULL,
1665     NULL);
1666
1667   NtStatus = NtCreateFile(&DestFile,
1668     FILE_WRITE_ACCESS,
1669     &ObjectAttributes,
1670     &IoStatusBlock,
1671     NULL,
1672     FILE_ATTRIBUTE_NORMAL,
1673     0,
1674     FILE_CREATE,
1675     FILE_SYNCHRONOUS_IO_ALERT,
1676     NULL,
1677     0);
1678   if (!NT_SUCCESS(NtStatus))
1679     {
1680       DPRINT("NtCreateFile() failed (%S) (%x).\n", DestName, NtStatus);
1681
1682       /* If file exists, ask to overwrite file */
1683       if (OverwriteHandler == NULL || OverwriteHandler(&File->File, FileName))
1684         {
1685           /* Create destination file, overwrite if it already exists */
1686           NtStatus = NtCreateFile(&DestFile,
1687             FILE_WRITE_ACCESS,
1688             &ObjectAttributes,
1689             &IoStatusBlock,
1690             NULL,
1691             FILE_ATTRIBUTE_NORMAL,
1692             0,
1693             FILE_OVERWRITE,
1694             FILE_SYNCHRONOUS_IO_ALERT,
1695             NULL,
1696             0);
1697           if (!NT_SUCCESS(NtStatus))
1698             {
1699               DPRINT("NtCreateFile() failed 2 (%S) (%x).\n", DestName, NtStatus);
1700               return CAB_STATUS_CANNOT_CREATE;
1701             }
1702         }
1703       else
1704         {
1705           DPRINT("File (%S) exists.\n", DestName);
1706           return CAB_STATUS_FILE_EXISTS;
1707         }
1708     }
1709   
1710   if (!ConvertDosDateTimeToFileTime(File->File.FileDate, File->File.FileTime, &FileTime))
1711     {
1712       NtClose(DestFile);
1713       DPRINT("DosDateTimeToFileTime() failed.\n");
1714       return CAB_STATUS_CANNOT_WRITE;
1715     }
1716
1717   NtStatus = NtQueryInformationFile(DestFile,
1718     &IoStatusBlock,
1719     &FileBasic,
1720     sizeof(FILE_BASIC_INFORMATION),
1721     FileBasicInformation);
1722   if (!NT_SUCCESS(Status))
1723     {
1724       DPRINT("NtQueryInformationFile() failed (%x).\n", NtStatus);
1725     }
1726   else
1727     {
1728       memcpy(&FileBasic.LastAccessTime, &FileTime, sizeof(FILETIME));
1729
1730       NtStatus = NtSetInformationFile(DestFile,
1731         &IoStatusBlock,
1732         &FileBasic,
1733         sizeof(FILE_BASIC_INFORMATION),
1734         FileBasicInformation);
1735       if (!NT_SUCCESS(NtStatus))
1736         {
1737           DPRINT("NtSetInformationFile() failed (%x).\n", NtStatus);
1738         }
1739     }
1740
1741   SetAttributesOnFile(File, DestFile);
1742   
1743   Buffer = RtlAllocateHeap(ProcessHeap, 0, CAB_BLOCKSIZE + 12); // This should be enough
1744   if (!Buffer)
1745     {
1746       NtClose(DestFile);
1747       DPRINT("Insufficient memory.\n");
1748       return CAB_STATUS_NOMEMORY; 
1749     }
1750   
1751   /* Call extract event handler */
1752   if (ExtractHandler != NULL)
1753     {
1754       ExtractHandler(&File->File, FileName);
1755     }
1756   
1757   /* Search to start of file */
1758   Offset = SeekInFile(FileHandle,
1759     File->DataBlock->AbsoluteOffset,
1760     NULL,
1761     SEEK_BEGIN,
1762     &NtStatus);
1763   if (!NT_SUCCESS(NtStatus))
1764     {
1765       DPRINT("SeekInFile() failed (%x).\n", NtStatus);
1766       return CAB_STATUS_INVALID_CAB;
1767     }
1768   
1769   Size   = File->File.FileSize;
1770   Offset = File->File.FileOffset;
1771   CurrentOffset = File->DataBlock->UncompOffset;
1772   
1773   Skip = TRUE;
1774   
1775   ReuseBlock = (CurrentDataNode == File->DataBlock);
1776   if (Size > 0)
1777     {
1778       do
1779         {
1780           DPRINT("CO (0x%X)    ReuseBlock (%d)    Offset (0x%X)   Size (%d)  BytesLeftInBlock (%d)\n",
1781                   File->DataBlock->UncompOffset, (UINT)ReuseBlock, Offset, Size,
1782                   BytesLeftInBlock);
1783       
1784           if (/*(CurrentDataNode != File->DataBlock) &&*/ (!ReuseBlock) || (BytesLeftInBlock <= 0))
1785             {
1786                     DPRINT("Filling buffer. ReuseBlock (%d)\n", (UINT)ReuseBlock);
1787       
1788               CurrentBuffer  = Buffer;
1789               TotalBytesRead = 0;
1790               do
1791                 {
1792                   DPRINT("Size (%d bytes).\n", Size);
1793   
1794                   if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) != 
1795                     CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
1796                     {
1797                       NtClose(DestFile);
1798                       RtlFreeHeap(ProcessHeap, 0, Buffer);
1799                       DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1800                       return CAB_STATUS_INVALID_CAB;
1801                     }
1802       
1803                   DPRINT("Data block: Checksum (0x%X)  CompSize (%d bytes)  UncompSize (%d bytes)  Offset (0x%X).\n",
1804                     (UINT)CFData.Checksum,
1805                     (UINT)CFData.CompSize,
1806                     (UINT)CFData.UncompSize,
1807                                       (UINT)SeekInFile(FileHandle, 0, NULL, SEEK_CURRENT, &NtStatus));
1808     
1809                   //ASSERT(CFData.CompSize <= CAB_BLOCKSIZE + 12);
1810     
1811                   BytesToRead = CFData.CompSize;
1812     
1813                                 DPRINT("Read: (0x%X,0x%X).\n",
1814                                         CurrentBuffer, Buffer);
1815
1816                   if (((Status = ReadBlock(CurrentBuffer, BytesToRead, &BytesRead)) != 
1817                       CAB_STATUS_SUCCESS) || (BytesToRead != BytesRead))
1818                     {
1819                       NtClose(DestFile);
1820                       RtlFreeHeap(ProcessHeap, 0, Buffer);
1821                       DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1822                       return CAB_STATUS_INVALID_CAB;
1823                     }
1824
1825                     /* FIXME: Does not work with files generated by makecab.exe */
1826 /*
1827                   if (CFData.Checksum != 0)
1828                     {
1829                       ULONG Checksum = ComputeChecksum(CurrentBuffer, BytesRead, 0);
1830                       if (Checksum != CFData.Checksum)
1831                         {
1832                           NtClose(DestFile);
1833                           RtlFreeHeap(ProcessHeap, 0, Buffer);
1834                           DPRINT("Bad checksum (is 0x%X, should be 0x%X).\n",
1835                             Checksum, CFData.Checksum);
1836                           return CAB_STATUS_INVALID_CAB;
1837                         }
1838                     }
1839 */
1840                   TotalBytesRead += BytesRead;
1841     
1842                   CurrentBuffer += BytesRead;
1843     
1844                   if (CFData.UncompSize == 0)
1845                     {
1846                       if (wcslen(DiskNext) == 0)
1847                           return CAB_STATUS_NOFILE;
1848
1849                       /* CloseCabinet() will destroy all file entries so in case
1850                          FileName refers to the FileName field of a CFFOLDER_NODE
1851                          structure, we have to save a copy of the filename */
1852                       wcscpy(TempName, FileName);
1853   
1854                       CloseCabinet();
1855   
1856                       CabinetSetCabinetName(CabinetNext);
1857   
1858                       if (DiskChangeHandler != NULL)
1859                         {
1860                           DiskChangeHandler(CabinetNext, DiskNext);
1861                         }
1862   
1863                       Status = CabinetOpen();
1864                       if (Status != CAB_STATUS_SUCCESS) 
1865                         return Status;
1866   
1867                       /* The first data block of the file will not be
1868                          found as it is located in the previous file */
1869                       Status = LocateFile(TempName, &File);
1870                       if (Status == CAB_STATUS_NOFILE)
1871                         {
1872                           DPRINT("Cannot locate file (%d).\n", (UINT)Status);
1873                           return Status;
1874                         }
1875     
1876                       /* The file is continued in the first data block in the folder */
1877                       File->DataBlock = CurrentFolderNode->DataListHead;
1878   
1879                       /* Search to start of file */
1880                       SeekInFile(FileHandle,
1881                         File->DataBlock->AbsoluteOffset,
1882                         NULL,
1883                         SEEK_BEGIN,
1884                         &NtStatus);
1885                       if (!NT_SUCCESS(NtStatus))
1886                         {
1887                           DPRINT("SeekInFile() failed (%x).\n", NtStatus);
1888                           return CAB_STATUS_INVALID_CAB;
1889                         }
1890     
1891                       DPRINT("Continuing extraction of file at uncompressed offset (0x%X)  Size (%d bytes)  AO (0x%X)  UO (0x%X).\n",
1892                           (UINT)File->File.FileOffset,
1893                           (UINT)File->File.FileSize,
1894                           (UINT)File->DataBlock->AbsoluteOffset,
1895                           (UINT)File->DataBlock->UncompOffset);
1896     
1897                       CurrentDataNode = File->DataBlock;
1898                       ReuseBlock = TRUE;
1899
1900                       RestartSearch = TRUE;
1901                     }
1902                 } while (CFData.UncompSize == 0);
1903
1904               DPRINT("TotalBytesRead (%d).\n", TotalBytesRead);
1905   
1906               Status = CodecUncompress(OutputBuffer, Buffer, TotalBytesRead, &BytesToWrite);
1907               if (Status != CS_SUCCESS)
1908                 {
1909                   NtClose(DestFile);
1910                   RtlFreeHeap(ProcessHeap, 0, Buffer);
1911                   DPRINT("Cannot uncompress block.\n");
1912                   if (Status == CS_NOMEMORY)
1913                     return CAB_STATUS_NOMEMORY;
1914                   return CAB_STATUS_INVALID_CAB;
1915                 }
1916
1917               if (BytesToWrite != CFData.UncompSize)
1918                 {
1919                   DPRINT("BytesToWrite (%d) != CFData.UncompSize (%d)\n",
1920                     BytesToWrite, CFData.UncompSize);
1921                   return CAB_STATUS_INVALID_CAB;
1922                 }
1923
1924               BytesLeftInBlock = BytesToWrite;
1925             }
1926           else
1927             {
1928               DPRINT("Using same buffer. ReuseBlock (%d)\n", (UINT)ReuseBlock);
1929     
1930               BytesToWrite = BytesLeftInBlock;
1931     
1932                         DPRINT("Seeking to absolute offset 0x%X.\n",
1933                                 CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
1934                   CurrentDataNode->Data.CompSize);
1935     
1936                         if (((Status = ReadBlock(&CFData, sizeof(CFDATA), &BytesRead)) != 
1937                                 CAB_STATUS_SUCCESS) || (BytesRead != sizeof(CFDATA)))
1938                 {
1939                                 NtClose(DestFile);
1940                   RtlFreeHeap(ProcessHeap, 0, Buffer);
1941                                 DPRINT("Cannot read from file (%d).\n", (UINT)Status);
1942                                 return CAB_STATUS_INVALID_CAB;
1943                         }
1944     
1945                         DPRINT("CFData.CompSize 0x%X  CFData.UncompSize 0x%X.\n",
1946                                 CFData.CompSize, CFData.UncompSize);
1947     
1948               /* Go to next data block */
1949               SeekInFile(FileHandle,
1950                 CurrentDataNode->AbsoluteOffset + sizeof(CFDATA) +
1951                 CurrentDataNode->Data.CompSize,
1952                 NULL,
1953                 SEEK_BEGIN,
1954                 &NtStatus);
1955               if (!NT_SUCCESS(NtStatus))
1956                 {
1957                   DPRINT("SeekInFile() failed (%x).\n", NtStatus);
1958                   return CAB_STATUS_INVALID_CAB;
1959                 }
1960
1961               ReuseBlock = FALSE;
1962             }
1963
1964           if (Skip)
1965               BytesSkipped = (Offset - CurrentOffset);
1966           else
1967               BytesSkipped = 0;
1968   
1969               BytesToWrite -= BytesSkipped;
1970   
1971                 if (Size < BytesToWrite)
1972             BytesToWrite = Size;
1973
1974           DPRINT("Offset (0x%X)  CurrentOffset (0x%X)  ToWrite (%d)  Skipped (%d)(%d)  Size (%d).\n",
1975             (UINT)Offset,
1976             (UINT)CurrentOffset,
1977             (UINT)BytesToWrite,
1978             (UINT)BytesSkipped, (UINT)Skip,
1979             (UINT)Size);
1980   
1981 //          if (!WriteFile(DestFile, (PVOID)((ULONG)OutputBuffer + BytesSkipped),
1982 //            BytesToWrite, &BytesWritten, NULL) ||
1983 //              (BytesToWrite != BytesWritten))
1984
1985           NtStatus = NtWriteFile(DestFile,
1986             NULL,
1987             NULL,
1988             NULL,
1989             &IoStatusBlock,
1990             (PVOID)((ULONG)OutputBuffer + BytesSkipped), 
1991             BytesToWrite,
1992             NULL,
1993             NULL);
1994           BytesWritten = BytesToWrite;
1995           if (!NT_SUCCESS(NtStatus))
1996             {
1997                     DPRINT("Status 0x%X.\n", NtStatus);
1998   
1999               NtClose(DestFile);
2000               RtlFreeHeap(ProcessHeap, 0, Buffer);
2001               DPRINT("Cannot write to file.\n");
2002               return CAB_STATUS_CANNOT_WRITE;
2003             }
2004           Size -= BytesToWrite;
2005   
2006             CurrentOffset += BytesToWrite;
2007
2008           /* Don't skip any more bytes */
2009           Skip = FALSE;
2010         } while (Size > 0);
2011     }
2012
2013   NtClose(DestFile);
2014
2015   RtlFreeHeap(ProcessHeap, 0, Buffer);
2016
2017   return CAB_STATUS_SUCCESS;
2018 }
2019
2020
2021 VOID
2022 CabinetSelectCodec(ULONG Id)
2023 /*
2024  * FUNCTION: Selects codec engine to use
2025  * ARGUMENTS:
2026  *     Id = Codec identifier
2027  */
2028 {
2029   if (CodecSelected)
2030     {
2031       if (Id == CodecId)
2032         return;
2033
2034       CodecSelected = FALSE;
2035     }
2036
2037   switch (Id)
2038     {
2039       case CAB_CODEC_RAW:
2040         CodecUncompress = RawCodecUncompress;
2041         break;
2042       case CAB_CODEC_MSZIP:
2043         CodecUncompress = MSZipCodecUncompress;
2044         break;
2045       default:
2046         return;
2047     }
2048
2049   CodecId = Id;
2050   CodecSelected = TRUE;
2051 }
2052
2053
2054 VOID
2055 CabinetSetEventHandlers(PCABINET_OVERWRITE Overwrite,
2056   PCABINET_EXTRACT Extract,
2057   PCABINET_DISK_CHANGE DiskChange)
2058 /*
2059  * FUNCTION: Set event handlers
2060  * ARGUMENTS:
2061  *     Overwrite = Handler called when a file is to be overwritten
2062  *     Extract = Handler called when a file is to be extracted
2063  *     DiskChange = Handler called when changing the disk
2064  */
2065 {
2066   OverwriteHandler = Overwrite;
2067   ExtractHandler = Extract;
2068   DiskChangeHandler = DiskChange;
2069 }
2070
2071
2072 PVOID
2073 CabinetGetCabinetReservedArea(PULONG Size)
2074 /*
2075  * FUNCTION: Get pointer to cabinet reserved area. NULL if none
2076  */
2077 {
2078   if (CabinetReservedArea != NULL)
2079     {
2080       if (Size != NULL)
2081         {
2082           *Size = CabinetReserved;
2083         }
2084       return CabinetReservedArea;
2085     }
2086   else
2087     {
2088       if (Size != NULL)
2089         {
2090           *Size = 0;
2091         }
2092       return NULL;
2093     }
2094 }