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