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