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