[USETUP]
[reactos.git] / reactos / base / setup / 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
11 #include "usetup.h"
12
13 #include <zlib.h>
14
15 #define NDEBUG
16 #include <debug.h>
17
18 #define SEEK_BEGIN 0
19 #define SEEK_CURRENT 1
20 #ifndef SEEK_END
21 #define SEEK_END 2
22 #endif
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 WCHAR DestPath[MAX_PATH];
47 static HANDLE FileHandle;
48 static HANDLE FileSectionHandle;
49 static PUCHAR FileBuffer;
50 static SIZE_T DestFileSize;
51 static SIZE_T FileSize;
52 static BOOL FileOpen = FALSE;
53 static PCFHEADER PCABHeader;
54 static PCFFOLDER CabinetFolders;
55 static ULONG CabinetReserved = 0;
56 static ULONG FolderReserved = 0;
57 static ULONG DataReserved = 0;
58 static ULONG CodecId;
59 static PCABINET_CODEC_UNCOMPRESS CodecUncompress = NULL;
60 static BOOL CodecSelected = FALSE;
61 static ULONG LastFileOffset = 0; // Uncompressed offset of last extracted file
62 static PCABINET_OVERWRITE OverwriteHandler = NULL;
63 static PCABINET_EXTRACT ExtractHandler = NULL;
64 static PCABINET_DISK_CHANGE DiskChangeHandler = NULL;
65 static z_stream ZStream;
66 static PVOID CabinetReservedArea = NULL;
67
68
69 /* Needed by zlib, but we don't want the dependency on msvcrt.dll */
70 void *__cdecl
71 malloc(size_t size)
72 {
73 return RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, size);
74 }
75
76 void __cdecl
77 free(void *ptr)
78 {
79 RtlFreeHeap(ProcessHeap, 0, ptr);
80 }
81
82 void *__cdecl
83 calloc(size_t nmemb, size_t size)
84 {
85 return (void *)RtlAllocateHeap(ProcessHeap, HEAP_ZERO_MEMORY, nmemb * size);
86 }
87
88 /* RAW codec */
89
90 /*
91 * FUNCTION: Uncompresses data in a buffer
92 * ARGUMENTS:
93 * OutputBuffer = Pointer to buffer to place uncompressed data
94 * InputBuffer = Pointer to buffer with data to be uncompressed
95 * InputLength = Length of input buffer before, and amount consumed after
96 * Negative to indicate that this is not the start of a new block
97 * OutputLength = Length of output buffer before, amount filled after
98 * Negative to indicate that this is not the end of the block
99 */
100 ULONG
101 RawCodecUncompress(PVOID OutputBuffer,
102 PVOID InputBuffer,
103 PLONG InputLength,
104 PLONG OutputLength)
105 {
106 LONG Len = min(abs(*InputLength), abs(*OutputLength));
107
108 memcpy(OutputBuffer, InputBuffer, Len);
109 *InputLength = *OutputLength = Len;
110
111 return CS_SUCCESS;
112 }
113
114 /* MSZIP codec */
115
116 /*
117 * FUNCTION: Uncompresses data in a buffer
118 * ARGUMENTS:
119 * OutputBuffer = Pointer to buffer to place uncompressed data
120 * InputBuffer = Pointer to buffer with data to be uncompressed
121 * InputLength = Length of input buffer before, and amount consumed after
122 * Negative to indicate that this is not the start of a new block
123 * OutputLength = Length of output buffer before, amount filled after
124 * Negative to indicate that this is not the end of the block
125 */
126 ULONG
127 MSZipCodecUncompress(PVOID OutputBuffer,
128 PVOID InputBuffer,
129 PLONG InputLength,
130 PLONG OutputLength)
131 {
132 USHORT Magic;
133 INT Status;
134
135 DPRINT("MSZipCodecUncompress(OutputBuffer = %x, InputBuffer = %x, "
136 "InputLength = %d, OutputLength = %d)\n", OutputBuffer,
137 InputBuffer, *InputLength, *OutputLength);
138 if (*InputLength > 0)
139 {
140 Magic = *(PUSHORT)InputBuffer;
141
142 if (Magic != MSZIP_MAGIC)
143 {
144 DPRINT("Bad MSZIP block header magic (0x%X)\n", Magic);
145 return CS_BADSTREAM;
146 }
147
148 ZStream.next_in = (PUCHAR)InputBuffer + 2;
149 ZStream.avail_in = *InputLength - 2;
150 ZStream.next_out = (PUCHAR)OutputBuffer;
151 ZStream.avail_out = abs(*OutputLength);
152
153 /* WindowBits is passed < 0 to tell that there is no zlib header.
154 * Note that in this case inflate *requires* an extra "dummy" byte
155 * after the compressed stream in order to complete decompression and
156 * return Z_STREAM_END.
157 */
158 Status = inflateInit2(&ZStream, -MAX_WBITS);
159 if (Status != Z_OK)
160 {
161 DPRINT("inflateInit2() returned (%d)\n", Status);
162 return CS_BADSTREAM;
163 }
164 ZStream.total_in = 2;
165 }
166 else
167 {
168 ZStream.avail_in = -*InputLength;
169 ZStream.next_in = (PUCHAR)InputBuffer;
170 ZStream.next_out = (PUCHAR)OutputBuffer;
171 ZStream.avail_out = abs(*OutputLength);
172 ZStream.total_in = 0;
173 }
174
175 ZStream.total_out = 0;
176 Status = inflate(&ZStream, Z_SYNC_FLUSH);
177 if (Status != Z_OK && Status != Z_STREAM_END)
178 {
179 DPRINT("inflate() returned (%d) (%s)\n", Status, ZStream.msg);
180 if (Status == Z_MEM_ERROR)
181 return CS_NOMEMORY;
182 return CS_BADSTREAM;
183 }
184
185 if (*OutputLength > 0)
186 {
187 Status = inflateEnd(&ZStream);
188 if (Status != Z_OK)
189 {
190 DPRINT("inflateEnd() returned (%d)\n", Status);
191 return CS_BADSTREAM;
192 }
193 }
194
195 *OutputLength = ZStream.total_out;
196 *InputLength = ZStream.total_in;
197
198 return CS_SUCCESS;
199 }
200
201 /* Memory functions */
202
203 voidpf
204 MSZipAlloc(voidpf opaque, uInt items, uInt size)
205 {
206 return (voidpf)RtlAllocateHeap(ProcessHeap, 0, items * size);
207 }
208
209 void
210 MSZipFree(voidpf opaque, voidpf address)
211 {
212 RtlFreeHeap(ProcessHeap, 0, address);
213 }
214
215 static BOOL
216 ConvertSystemTimeToFileTime(CONST SYSTEMTIME *lpSystemTime,
217 LPFILETIME lpFileTime)
218 {
219 TIME_FIELDS TimeFields;
220 LARGE_INTEGER liTime;
221
222 TimeFields.Year = lpSystemTime->wYear;
223 TimeFields.Month = lpSystemTime->wMonth;
224 TimeFields.Day = lpSystemTime->wDay;
225 TimeFields.Hour = lpSystemTime->wHour;
226 TimeFields.Minute = lpSystemTime->wMinute;
227 TimeFields.Second = lpSystemTime->wSecond;
228 TimeFields.Milliseconds = lpSystemTime->wMilliseconds;
229
230 if (RtlTimeFieldsToTime(&TimeFields, &liTime))
231 {
232 lpFileTime->dwLowDateTime = liTime.u.LowPart;
233 lpFileTime->dwHighDateTime = liTime.u.HighPart;
234 return TRUE;
235 }
236
237 return FALSE;
238 }
239
240 static BOOL
241 ConvertDosDateTimeToFileTime(WORD wFatDate,
242 WORD wFatTime,
243 LPFILETIME lpFileTime)
244 {
245 PDOSTIME pdtime = (PDOSTIME)&wFatTime;
246 PDOSDATE pddate = (PDOSDATE)&wFatDate;
247 SYSTEMTIME SystemTime;
248
249 if (lpFileTime == NULL)
250 return FALSE;
251
252 SystemTime.wMilliseconds = 0;
253 SystemTime.wSecond = pdtime->Second;
254 SystemTime.wMinute = pdtime->Minute;
255 SystemTime.wHour = pdtime->Hour;
256
257 SystemTime.wDay = pddate->Day;
258 SystemTime.wMonth = pddate->Month;
259 SystemTime.wYear = 1980 + pddate->Year;
260
261 ConvertSystemTimeToFileTime(&SystemTime, lpFileTime);
262
263 return TRUE;
264 }
265
266 /*
267 * FUNCTION: Returns a pointer to file name
268 * ARGUMENTS:
269 * Path = Pointer to string with pathname
270 * RETURNS:
271 * Pointer to filename
272 */
273 static PWCHAR
274 GetFileName(PWCHAR Path)
275 {
276 ULONG i, j;
277
278 j = i = 0;
279
280 while (Path[i++])
281 {
282 if (Path[i - 1] == L'\\')
283 j = i;
284 }
285
286 return Path + j;
287 }
288
289 /*
290 * FUNCTION: Removes a file name from a path
291 * ARGUMENTS:
292 * Path = Pointer to string with path
293 */
294 static VOID
295 RemoveFileName(PWCHAR Path)
296 {
297 PWCHAR FileName;
298 DWORD i;
299
300 i = 0;
301 FileName = GetFileName(Path + i);
302
303 if (FileName != Path + i && FileName[-1] == L'\\')
304 FileName--;
305
306 if (FileName == Path + i && FileName[0] == L'\\')
307 FileName++;
308
309 FileName[0] = 0;
310 }
311
312 /*
313 * FUNCTION: Sets attributes on a file
314 * ARGUMENTS:
315 * File = Pointer to CFFILE node for file
316 * RETURNS:
317 * Status of operation
318 */
319 static BOOL
320 SetAttributesOnFile(PCFFILE File,
321 HANDLE hFile)
322 {
323 FILE_BASIC_INFORMATION FileBasic;
324 IO_STATUS_BLOCK IoStatusBlock;
325 NTSTATUS NtStatus;
326 ULONG Attributes = 0;
327
328 if (File->Attributes & CAB_ATTRIB_READONLY)
329 Attributes |= FILE_ATTRIBUTE_READONLY;
330
331 if (File->Attributes & CAB_ATTRIB_HIDDEN)
332 Attributes |= FILE_ATTRIBUTE_HIDDEN;
333
334 if (File->Attributes & CAB_ATTRIB_SYSTEM)
335 Attributes |= FILE_ATTRIBUTE_SYSTEM;
336
337 if (File->Attributes & CAB_ATTRIB_DIRECTORY)
338 Attributes |= FILE_ATTRIBUTE_DIRECTORY;
339
340 if (File->Attributes & CAB_ATTRIB_ARCHIVE)
341 Attributes |= FILE_ATTRIBUTE_ARCHIVE;
342
343 NtStatus = NtQueryInformationFile(hFile,
344 &IoStatusBlock,
345 &FileBasic,
346 sizeof(FILE_BASIC_INFORMATION),
347 FileBasicInformation);
348 if (!NT_SUCCESS(NtStatus))
349 {
350 DPRINT("NtQueryInformationFile() failed (%x)\n", NtStatus);
351 }
352 else
353 {
354 FileBasic.FileAttributes = Attributes;
355
356 NtStatus = NtSetInformationFile(hFile,
357 &IoStatusBlock,
358 &FileBasic,
359 sizeof(FILE_BASIC_INFORMATION),
360 FileBasicInformation);
361 if (!NT_SUCCESS(NtStatus))
362 {
363 DPRINT("NtSetInformationFile() failed (%x)\n", NtStatus);
364 }
365 }
366
367 return NT_SUCCESS(NtStatus);
368 }
369
370 /*
371 * FUNCTION: Closes the current cabinet
372 * RETURNS:
373 * Status of operation
374 */
375 static ULONG
376 CloseCabinet(VOID)
377 {
378 if (FileBuffer)
379 {
380 NtUnmapViewOfSection(NtCurrentProcess(), FileBuffer);
381 NtClose(FileSectionHandle);
382 NtClose(FileHandle);
383 FileBuffer = NULL;
384 }
385
386 return 0;
387 }
388
389 /*
390 * FUNCTION: Initialize archiver
391 */
392 VOID
393 CabinetInitialize(VOID)
394 {
395 ZStream.zalloc = MSZipAlloc;
396 ZStream.zfree = MSZipFree;
397 ZStream.opaque = (voidpf)0;
398
399 FileOpen = FALSE;
400 wcscpy(DestPath, L"");
401
402 CodecId = CAB_CODEC_RAW;
403 CodecSelected = TRUE;
404
405 FolderUncompSize = 0;
406 BytesLeftInBlock = 0;
407 CabinetReserved = 0;
408 FolderReserved = 0;
409 DataReserved = 0;
410 CabinetReservedArea = NULL;
411 LastFileOffset = 0;
412 }
413
414 /*
415 * FUNCTION: Cleanup archiver
416 */
417 VOID
418 CabinetCleanup(VOID)
419 {
420 CabinetClose();
421 }
422
423 /*
424 * FUNCTION: Normalizes a path
425 * ARGUMENTS:
426 * Path = Pointer to string with pathname
427 * Length = Number of characters in Path
428 * RETURNS:
429 * TRUE if there was enough room in Path, or FALSE
430 */
431 BOOL
432 CabinetNormalizePath(PWCHAR Path,
433 ULONG Length)
434 {
435 ULONG n;
436 BOOL Ok;
437
438 n = wcslen(Path);
439 Ok = (n + 1) < Length;
440
441 if (n != 0 && Path[n - 1] != L'\\' && Ok)
442 {
443 Path[n] = L'\\';
444 Path[n + 1] = 0;
445 }
446
447 return Ok;
448 }
449
450 /*
451 * FUNCTION: Returns pointer to cabinet file name
452 * RETURNS:
453 * Pointer to string with name of cabinet
454 */
455 PWCHAR
456 CabinetGetCabinetName(VOID)
457 {
458 return CabinetName;
459 }
460
461 /*
462 * FUNCTION: Sets cabinet file name
463 * ARGUMENTS:
464 * FileName = Pointer to string with name of cabinet
465 */
466 VOID
467 CabinetSetCabinetName(PWCHAR FileName)
468 {
469 wcscpy(CabinetName, FileName);
470 }
471
472 /*
473 * FUNCTION: Sets destination path
474 * ARGUMENTS:
475 * DestinationPath = Pointer to string with name of destination path
476 */
477 VOID
478 CabinetSetDestinationPath(PWCHAR DestinationPath)
479 {
480 wcscpy(DestPath, DestinationPath);
481
482 if (wcslen(DestPath) > 0)
483 CabinetNormalizePath(DestPath, MAX_PATH);
484 }
485
486 /*
487 * FUNCTION: Returns destination path
488 * RETURNS:
489 * Pointer to string with name of destination path
490 */
491 PWCHAR
492 CabinetGetDestinationPath(VOID)
493 {
494 return DestPath;
495 }
496
497 /*
498 * FUNCTION: Opens a cabinet file
499 * RETURNS:
500 * Status of operation
501 */
502 ULONG
503 CabinetOpen(VOID)
504 {
505 PUCHAR Buffer;
506 UNICODE_STRING ustring;
507 ANSI_STRING astring;
508
509 if (!FileOpen)
510 {
511 OBJECT_ATTRIBUTES ObjectAttributes;
512 IO_STATUS_BLOCK IoStatusBlock;
513 UNICODE_STRING FileName;
514 NTSTATUS NtStatus;
515
516 RtlInitUnicodeString(&FileName, CabinetName);
517
518 InitializeObjectAttributes(&ObjectAttributes,
519 &FileName,
520 OBJ_CASE_INSENSITIVE,
521 NULL, NULL);
522
523 NtStatus = NtOpenFile(&FileHandle,
524 GENERIC_READ | SYNCHRONIZE,
525 &ObjectAttributes,
526 &IoStatusBlock,
527 FILE_SHARE_READ,
528 FILE_SYNCHRONOUS_IO_NONALERT);
529
530 if (!NT_SUCCESS(NtStatus))
531 {
532 DPRINT("Cannot open file (%S) (%x)\n", CabinetName, NtStatus);
533 return CAB_STATUS_CANNOT_OPEN;
534 }
535
536 FileOpen = TRUE;
537
538 NtStatus = NtCreateSection(&FileSectionHandle,
539 SECTION_ALL_ACCESS,
540 0, 0,
541 PAGE_READONLY,
542 SEC_COMMIT,
543 FileHandle);
544
545 if (!NT_SUCCESS(NtStatus))
546 {
547 DPRINT("NtCreateSection failed: %x\n", NtStatus);
548 return CAB_STATUS_NOMEMORY;
549 }
550
551 FileBuffer = 0;
552 FileSize = 0;
553
554 NtStatus = NtMapViewOfSection(FileSectionHandle,
555 NtCurrentProcess(),
556 (PVOID *)&FileBuffer,
557 0, 0, 0,
558 &FileSize,
559 ViewUnmap,
560 0,
561 PAGE_READONLY);
562
563 if (!NT_SUCCESS(NtStatus))
564 {
565 DPRINT("NtMapViewOfSection failed: %x\n", NtStatus);
566 return CAB_STATUS_NOMEMORY;
567 }
568
569 DPRINT("Cabinet file %S opened and mapped to %x\n", CabinetName, FileBuffer);
570 PCABHeader = (PCFHEADER) FileBuffer;
571
572 /* Check header */
573 if (FileSize <= sizeof(CFHEADER) ||
574 PCABHeader->Signature != CAB_SIGNATURE ||
575 PCABHeader->Version != CAB_VERSION ||
576 PCABHeader->FolderCount == 0 ||
577 PCABHeader->FileCount == 0 ||
578 PCABHeader->FileTableOffset < sizeof(CFHEADER))
579 {
580 CloseCabinet();
581 DPRINT("File has invalid header\n");
582 return CAB_STATUS_INVALID_CAB;
583 }
584
585 Buffer = (PUCHAR)(PCABHeader + 1);
586
587 /* Read/skip any reserved bytes */
588 if (PCABHeader->Flags & CAB_FLAG_RESERVE)
589 {
590 CabinetReserved = *(PUSHORT)Buffer;
591 Buffer += 2;
592 FolderReserved = *Buffer;
593 Buffer++;
594 DataReserved = *Buffer;
595 Buffer++;
596
597 if (CabinetReserved > 0)
598 {
599 CabinetReservedArea = Buffer;
600 Buffer += CabinetReserved;
601 }
602 }
603
604 if (PCABHeader->Flags & CAB_FLAG_HASPREV)
605 {
606 /* The previous cabinet file is in
607 the same directory as the current */
608 wcscpy(CabinetPrev, CabinetName);
609 RemoveFileName(CabinetPrev);
610 CabinetNormalizePath(CabinetPrev, 256);
611 RtlInitAnsiString(&astring, (LPSTR)Buffer);
612 ustring.Length = wcslen(CabinetPrev);
613 ustring.Buffer = CabinetPrev + ustring.Length;
614 ustring.MaximumLength = sizeof(CabinetPrev) - ustring.Length;
615 RtlAnsiStringToUnicodeString(&ustring, &astring, FALSE);
616 Buffer += astring.Length + 1;
617
618 /* Read label of prev disk */
619 RtlInitAnsiString(&astring, (LPSTR)Buffer);
620 ustring.Length = 0;
621 ustring.Buffer = DiskPrev;
622 ustring.MaximumLength = sizeof(DiskPrev);
623 RtlAnsiStringToUnicodeString(&ustring, &astring, FALSE);
624 Buffer += astring.Length + 1;
625 }
626 else
627 {
628 wcscpy(CabinetPrev, L"");
629 wcscpy(DiskPrev, L"");
630 }
631
632 if (PCABHeader->Flags & CAB_FLAG_HASNEXT)
633 {
634 /* The next cabinet file is in
635 the same directory as the previous */
636 wcscpy(CabinetNext, CabinetName);
637 RemoveFileName(CabinetNext);
638 CabinetNormalizePath(CabinetNext, 256);
639 RtlInitAnsiString(&astring, (LPSTR)Buffer);
640 ustring.Length = wcslen(CabinetNext);
641 ustring.Buffer = CabinetNext + ustring.Length;
642 ustring.MaximumLength = sizeof(CabinetNext) - ustring.Length;
643 RtlAnsiStringToUnicodeString(&ustring, &astring, FALSE);
644 Buffer += astring.Length + 1;
645
646 /* Read label of next disk */
647 RtlInitAnsiString(&astring, (LPSTR)Buffer);
648 ustring.Length = 0;
649 ustring.Buffer = DiskNext;
650 ustring.MaximumLength = sizeof(DiskNext);
651 RtlAnsiStringToUnicodeString(&ustring, &astring, FALSE);
652 Buffer += astring.Length + 1;
653 }
654 else
655 {
656 wcscpy(CabinetNext, L"");
657 wcscpy(DiskNext, L"");
658 }
659 CabinetFolders = (PCFFOLDER)Buffer;
660 }
661
662 DPRINT("CabinetOpen returning SUCCESS\n");
663 return CAB_STATUS_SUCCESS;
664 }
665
666 /*
667 * FUNCTION: Closes the cabinet file
668 */
669 VOID
670 CabinetClose(VOID)
671 {
672 if (FileOpen)
673 {
674 CloseCabinet();
675 FileOpen = FALSE;
676 }
677 }
678
679 /*
680 * FUNCTION: Finds the first file in the cabinet that matches a search criteria
681 * ARGUMENTS:
682 * FileName = Pointer to search criteria
683 * Search = Pointer to search structure
684 * RETURNS:
685 * Status of operation
686 */
687 ULONG
688 CabinetFindFirst(PWCHAR FileName,
689 PCAB_SEARCH Search)
690 {
691 DPRINT("CabinetFindFirst( FileName = %S )\n", FileName);
692 wcsncpy(Search->Search, FileName, MAX_PATH);
693 wcsncpy(Search->Cabinet, CabinetName, MAX_PATH);
694 Search->File = 0;
695 return CabinetFindNext(Search);
696 }
697
698 /*
699 * FUNCTION: Finds the next file in the cabinet that matches a search criteria
700 * ARGUMENTS:
701 * FileName = Pointer to search criteria
702 * Search = Pointer to search structure
703 * RETURNS:
704 * Status of operation
705 */
706 ULONG
707 CabinetFindNextFileSequential(PWCHAR FileName,
708 PCAB_SEARCH Search)
709 {
710 DPRINT("CabinetFindNextFileSequential( FileName = %S )\n", FileName);
711 wcsncpy(Search->Search, FileName, MAX_PATH);
712 return CabinetFindNext(Search);
713 }
714
715 /*
716 * FUNCTION: Finds next file in the cabinet that matches a search criteria
717 * ARGUMENTS:
718 * Search = Pointer to search structure
719 * RETURNS:
720 * Status of operation
721 */
722 ULONG
723 CabinetFindNext(PCAB_SEARCH Search)
724 {
725 PCFFILE Prev;
726 ANSI_STRING AnsiString;
727 UNICODE_STRING UnicodeString;
728 WCHAR FileName[MAX_PATH];
729
730 if (wcscmp(Search->Cabinet, CabinetName) != 0)
731 {
732 /* restart search of cabinet has changed since last find */
733 Search->File = 0;
734 }
735
736 if (!Search->File)
737 {
738 /* starting new search or cabinet */
739 Search->File = (PCFFILE)(FileBuffer + PCABHeader->FileTableOffset);
740 Search->Index = 0;
741 Prev = 0;
742 }
743 else
744 Prev = Search->File;
745
746 while (TRUE)
747 {
748 /* look at each file in the archive and see if we found a match */
749 if (Search->File->FolderIndex == 0xFFFD ||
750 Search->File->FolderIndex == 0xFFFF)
751 {
752 /* skip files continued from previous cab */
753 DPRINT("Skipping file (%s): FileOffset (0x%X), "
754 "LastFileOffset (0x%X)\n", (char *)(Search->File + 1),
755 Search->File->FileOffset, LastFileOffset);
756 }
757 else
758 {
759 // FIXME: check for match against search criteria
760 if (Search->File != Prev)
761 {
762 if (Prev == NULL || Search->File->FolderIndex != Prev->FolderIndex)
763 {
764 Search->CFData = NULL;
765 Search->Offset = 0;
766 }
767
768 /* don't match the file we started with */
769 if (wcscmp(Search->Search, L"*") == 0)
770 {
771 /* take any file */
772 break;
773 }
774 else
775 {
776 /* otherwise, try to match the exact file name */
777 RtlInitAnsiString(&AnsiString, Search->File->FileName);
778 UnicodeString.Buffer = FileName;
779 UnicodeString.Buffer[0] = 0;
780 UnicodeString.Length = 0;
781 UnicodeString.MaximumLength = sizeof(FileName);
782 RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, FALSE);
783 if (wcscmp(Search->Search, UnicodeString.Buffer) == 0)
784 break;
785 }
786 }
787 }
788
789 /* if we make it here we found no match, so move to the next file */
790 Search->Index++;
791 if (Search->Index >= PCABHeader->FileCount)
792 {
793 /* we have reached the end of this cabinet */
794 DPRINT("End of cabinet reached\n");
795 return CAB_STATUS_NOFILE;
796 }
797 else
798 Search->File = (PCFFILE)(strchr((char *)(Search->File + 1), 0) + 1);
799 }
800
801 DPRINT("Found file %s\n", Search->File->FileName);
802 return CAB_STATUS_SUCCESS;
803 }
804
805 #if 0
806 int
807 Validate()
808 {
809 return (int)RtlValidateHeap(ProcessHeap, 0, 0);
810 }
811 #endif
812
813 /*
814 * FUNCTION: Extracts a file from the cabinet
815 * ARGUMENTS:
816 * Search = Pointer to PCAB_SEARCH structure used to locate the file
817 * RETURNS
818 * Status of operation
819 */
820 ULONG
821 CabinetExtractFile(PCAB_SEARCH Search)
822 {
823 ULONG Size; // remaining file bytes to decompress
824 ULONG CurrentOffset; // current uncompressed offset within the folder
825 PUCHAR CurrentBuffer; // current pointer to compressed data in the block
826 LONG RemainingBlock; // remaining comp data in the block
827 HANDLE DestFile;
828 HANDLE DestFileSection;
829 PVOID DestFileBuffer; // mapped view of dest file
830 PVOID CurrentDestBuffer; // pointer to the current position in the dest view
831 PCFDATA CFData; // current data block
832 ULONG Status;
833 FILETIME FileTime;
834 WCHAR DestName[MAX_PATH];
835 NTSTATUS NtStatus;
836 UNICODE_STRING UnicodeString;
837 ANSI_STRING AnsiString;
838 IO_STATUS_BLOCK IoStatusBlock;
839 OBJECT_ATTRIBUTES ObjectAttributes;
840 FILE_BASIC_INFORMATION FileBasic;
841 PCFFOLDER CurrentFolder;
842 LARGE_INTEGER MaxDestFileSize;
843 LONG InputLength, OutputLength;
844 char Junk[512];
845
846 if (wcscmp(Search->Cabinet, CabinetName) != 0)
847 {
848 /* the file is not in the current cabinet */
849 DPRINT("File is not in this cabinet (%S != %S)\n",
850 Search->Cabinet, CabinetName);
851 return CAB_STATUS_NOFILE;
852 }
853
854 /* look up the folder that the file specifies */
855 if (Search->File->FolderIndex == 0xFFFD ||
856 Search->File->FolderIndex == 0xFFFF)
857 {
858 /* folder is continued from previous cabinet,
859 that shouldn't happen here */
860 return CAB_STATUS_NOFILE;
861 }
862 else if (Search->File->FolderIndex == 0xFFFE)
863 {
864 /* folder is the last in this cabinet and continues into next */
865 CurrentFolder = &CabinetFolders[PCABHeader->FolderCount - 1];
866 }
867 else
868 {
869 /* folder is completely contained within this cabinet */
870 CurrentFolder = &CabinetFolders[Search->File->FolderIndex];
871 }
872
873 switch (CurrentFolder->CompressionType & CAB_COMP_MASK)
874 {
875 case CAB_COMP_NONE:
876 CabinetSelectCodec(CAB_CODEC_RAW);
877 break;
878 case CAB_COMP_MSZIP:
879 CabinetSelectCodec(CAB_CODEC_MSZIP);
880 break;
881 default:
882 return CAB_STATUS_UNSUPPCOMP;
883 }
884
885 DPRINT("Extracting file at uncompressed offset (0x%X) Size (%d bytes)\n",
886 (UINT)Search->File->FileOffset, (UINT)Search->File->FileSize);
887
888 RtlInitAnsiString(&AnsiString, Search->File->FileName);
889 wcscpy(DestName, DestPath);
890 UnicodeString.MaximumLength = sizeof(DestName) - wcslen(DestName) * sizeof(WCHAR);
891 UnicodeString.Buffer = DestName + wcslen(DestName);
892 UnicodeString.Length = 0;
893 RtlAnsiStringToUnicodeString(&UnicodeString, &AnsiString, FALSE);
894
895 /* Create destination file, fail if it already exists */
896 RtlInitUnicodeString(&UnicodeString, DestName);
897
898 InitializeObjectAttributes(&ObjectAttributes,
899 &UnicodeString,
900 OBJ_CASE_INSENSITIVE,
901 NULL, NULL);
902
903 NtStatus = NtCreateFile(&DestFile,
904 GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
905 &ObjectAttributes,
906 &IoStatusBlock,
907 NULL,
908 FILE_ATTRIBUTE_NORMAL,
909 0,
910 FILE_CREATE,
911 FILE_SYNCHRONOUS_IO_NONALERT,
912 NULL, 0);
913
914 if (!NT_SUCCESS(NtStatus))
915 {
916 DPRINT("NtCreateFile() failed (%S) (%x)\n", DestName, NtStatus);
917
918 /* If file exists, ask to overwrite file */
919 if (OverwriteHandler == NULL || OverwriteHandler(Search->File, DestName))
920 {
921 /* Create destination file, overwrite if it already exists */
922 NtStatus = NtCreateFile(&DestFile,
923 GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
924 &ObjectAttributes,
925 &IoStatusBlock,
926 NULL,
927 FILE_ATTRIBUTE_NORMAL,
928 0,
929 FILE_OVERWRITE,
930 FILE_SYNCHRONOUS_IO_ALERT,
931 NULL, 0);
932
933 if (!NT_SUCCESS(NtStatus))
934 {
935 DPRINT("NtCreateFile() failed (%S) (%x)\n", DestName, NtStatus);
936 return CAB_STATUS_CANNOT_CREATE;
937 }
938 }
939 else
940 {
941 DPRINT("File (%S) exists\n", DestName);
942 return CAB_STATUS_FILE_EXISTS;
943 }
944 }
945
946 MaxDestFileSize.QuadPart = Search->File->FileSize;
947 NtStatus = NtCreateSection(&DestFileSection,
948 SECTION_ALL_ACCESS,
949 0,
950 &MaxDestFileSize,
951 PAGE_READWRITE,
952 SEC_COMMIT,
953 DestFile);
954
955 if (!NT_SUCCESS(NtStatus))
956 {
957 DPRINT("NtCreateSection failed: %x\n", NtStatus);
958 Status = CAB_STATUS_NOMEMORY;
959 goto CloseDestFile;
960 }
961
962 DestFileBuffer = 0;
963 DestFileSize = 0;
964 NtStatus = NtMapViewOfSection(DestFileSection,
965 NtCurrentProcess(),
966 &DestFileBuffer,
967 0, 0, 0,
968 &DestFileSize,
969 ViewUnmap,
970 0,
971 PAGE_READWRITE);
972
973 if (!NT_SUCCESS(NtStatus))
974 {
975 DPRINT("NtMapViewOfSection failed: %x\n", NtStatus);
976 Status = CAB_STATUS_NOMEMORY;
977 goto CloseDestFileSection;
978 }
979
980 CurrentDestBuffer = DestFileBuffer;
981 if (!ConvertDosDateTimeToFileTime(Search->File->FileDate,
982 Search->File->FileTime,
983 &FileTime))
984 {
985 DPRINT("DosDateTimeToFileTime() failed\n");
986 Status = CAB_STATUS_CANNOT_WRITE;
987 goto UnmapDestFile;
988 }
989
990 NtStatus = NtQueryInformationFile(DestFile,
991 &IoStatusBlock,
992 &FileBasic,
993 sizeof(FILE_BASIC_INFORMATION),
994 FileBasicInformation);
995 if (!NT_SUCCESS(NtStatus))
996 {
997 DPRINT("NtQueryInformationFile() failed (%x)\n", NtStatus);
998 }
999 else
1000 {
1001 memcpy(&FileBasic.LastAccessTime, &FileTime, sizeof(FILETIME));
1002
1003 NtStatus = NtSetInformationFile(DestFile,
1004 &IoStatusBlock,
1005 &FileBasic,
1006 sizeof(FILE_BASIC_INFORMATION),
1007 FileBasicInformation);
1008 if (!NT_SUCCESS(NtStatus))
1009 {
1010 DPRINT("NtSetInformationFile() failed (%x)\n", NtStatus);
1011 }
1012 }
1013
1014 SetAttributesOnFile(Search->File, DestFile);
1015
1016 /* Call extract event handler */
1017 if (ExtractHandler != NULL)
1018 {
1019 ExtractHandler(Search->File, DestName);
1020 }
1021
1022 if (Search->CFData)
1023 CFData = Search->CFData;
1024 else
1025 CFData = (PCFDATA)(CabinetFolders[Search->File->FolderIndex].DataOffset + FileBuffer);
1026
1027 CurrentOffset = Search->Offset;
1028 while (CurrentOffset + CFData->UncompSize <= Search->File->FileOffset)
1029 {
1030 /* walk the data blocks until we reach
1031 the one containing the start of the file */
1032 CurrentOffset += CFData->UncompSize;
1033 CFData = (PCFDATA)((char *)(CFData + 1) + DataReserved + CFData->CompSize);
1034 }
1035
1036 Search->CFData = CFData;
1037 Search->Offset = CurrentOffset;
1038
1039 /* now decompress and discard any data in
1040 the block before the start of the file */
1041
1042 /* start of comp data */
1043 CurrentBuffer = ((unsigned char *)(CFData + 1)) + DataReserved;
1044 RemainingBlock = CFData->CompSize;
1045 InputLength = RemainingBlock;
1046
1047 while (CurrentOffset < Search->File->FileOffset)
1048 {
1049 /* compute remaining uncomp bytes to start
1050 of file, bounded by sizeof junk */
1051 OutputLength = Search->File->FileOffset - CurrentOffset;
1052 if (OutputLength > (LONG)sizeof(Junk))
1053 OutputLength = sizeof (Junk);
1054
1055 /* negate to signal NOT end of block */
1056 OutputLength = -OutputLength;
1057 CodecUncompress(Junk, CurrentBuffer, &InputLength, &OutputLength);
1058 /* add the uncomp bytes extracted to current folder offset */
1059 CurrentOffset += OutputLength;
1060 /* add comp bytes consumed to CurrentBuffer */
1061 CurrentBuffer += InputLength;
1062 /* subtract bytes consumed from bytes remaining in block */
1063 RemainingBlock -= InputLength;
1064 /* neg for resume decompression of the same block */
1065 InputLength = -RemainingBlock;
1066 }
1067
1068 /* now CurrentBuffer points to the first comp byte
1069 of the file, so we can begin decompressing */
1070
1071 /* Size = remaining uncomp bytes of the file to decompress */
1072 Size = Search->File->FileSize;
1073 while (Size > 0)
1074 {
1075 OutputLength = Size;
1076 DPRINT("Decompressing block at %x with RemainingBlock = %d, Size = %d\n",
1077 CurrentBuffer, RemainingBlock, Size);
1078
1079 Status = CodecUncompress(CurrentDestBuffer,
1080 CurrentBuffer,
1081 &InputLength,
1082 &OutputLength);
1083
1084 if (Status != CS_SUCCESS)
1085 {
1086 DPRINT("Cannot uncompress block\n");
1087 if (Status == CS_NOMEMORY)
1088 Status = CAB_STATUS_NOMEMORY;
1089 Status = CAB_STATUS_INVALID_CAB;
1090 goto UnmapDestFile;
1091 }
1092
1093 /* advance dest buffer by bytes produced */
1094 CurrentDestBuffer = (PVOID)((ULONG_PTR)CurrentDestBuffer + OutputLength);
1095 /* advance src buffer by bytes consumed */
1096 CurrentBuffer += InputLength;
1097 /* reduce remaining file bytes by bytes produced */
1098 Size -= OutputLength;
1099 /* reduce remaining block size by bytes consumed */
1100 RemainingBlock -= InputLength;
1101 if (Size > 0 && RemainingBlock == 0)
1102 {
1103 /* used up this block, move on to the next */
1104 DPRINT("Out of block data\n");
1105 CFData = (PCFDATA)CurrentBuffer;
1106 RemainingBlock = CFData->CompSize;
1107 CurrentBuffer = (unsigned char *)(CFData + 1) + DataReserved;
1108 InputLength = RemainingBlock;
1109 }
1110 }
1111
1112 Status = CAB_STATUS_SUCCESS;
1113
1114 UnmapDestFile:
1115 NtUnmapViewOfSection(NtCurrentProcess(), DestFileBuffer);
1116
1117 CloseDestFileSection:
1118 NtClose(DestFileSection);
1119
1120 CloseDestFile:
1121 NtClose(DestFile);
1122
1123 return Status;
1124 }
1125
1126 /*
1127 * FUNCTION: Selects codec engine to use
1128 * ARGUMENTS:
1129 * Id = Codec identifier
1130 */
1131 VOID
1132 CabinetSelectCodec(ULONG Id)
1133 {
1134 if (CodecSelected)
1135 {
1136 if (Id == CodecId)
1137 return;
1138
1139 CodecSelected = FALSE;
1140 }
1141
1142 switch (Id)
1143 {
1144 case CAB_CODEC_RAW:
1145 CodecUncompress = RawCodecUncompress;
1146 break;
1147 case CAB_CODEC_MSZIP:
1148 CodecUncompress = MSZipCodecUncompress;
1149 break;
1150 default:
1151 return;
1152 }
1153
1154 CodecId = Id;
1155 CodecSelected = TRUE;
1156 }
1157
1158 /*
1159 * FUNCTION: Set event handlers
1160 * ARGUMENTS:
1161 * Overwrite = Handler called when a file is to be overwritten
1162 * Extract = Handler called when a file is to be extracted
1163 * DiskChange = Handler called when changing the disk
1164 */
1165 VOID
1166 CabinetSetEventHandlers(PCABINET_OVERWRITE Overwrite,
1167 PCABINET_EXTRACT Extract,
1168 PCABINET_DISK_CHANGE DiskChange)
1169 {
1170 OverwriteHandler = Overwrite;
1171 ExtractHandler = Extract;
1172 DiskChangeHandler = DiskChange;
1173 }
1174
1175 /*
1176 * FUNCTION: Get pointer to cabinet reserved area. NULL if none
1177 */
1178 PVOID
1179 CabinetGetCabinetReservedArea(PULONG Size)
1180 {
1181 if (CabinetReservedArea != NULL)
1182 {
1183 if (Size != NULL)
1184 {
1185 *Size = CabinetReserved;
1186 }
1187
1188 return CabinetReservedArea;
1189 }
1190 else
1191 {
1192 if (Size != NULL)
1193 {
1194 *Size = 0;
1195 }
1196
1197 return NULL;
1198 }
1199 }