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