// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. #include "precomp.h" //--------------------------------------------------------------------- // CABINET EXTRACTION //--------------------------------------------------------------------- // Globals make this code unsuited for multhreaded use, // but FDI doesn't provide any other way to pass context. // Handle to the FDI (cab extraction) engine. Need access to this in a callback. static HFDI g_hfdi; // FDI is not unicode-aware, so avoid passing these paths through the callbacks. static const wchar_t* g_szExtractDir; static const wchar_t* g_szCabFile; // Offset into the source file where the cabinet really starts. // Used to trick FDI into extracting from a concatenated cabinet. static int g_lCabOffset; // Use the secure CRT version of _wsopen if available. #ifdef __GOT_SECURE_LIB__ #define _wsopen__s(hf,file,oflag,shflag,pmode) _wsopen_s(&hf,file,oflag,shflag,pmode) #else #define _wsopen__s(hf,file,oflag,shflag,pmode) hf = _wsopen(file,oflag,shflag,pmode) #endif /// /// FDI callback to open a cabinet file. /// /// Name of the file to be opened. This parameter /// is ignored since with our limited use this method is only ever called /// to open the main cabinet file. /// Type of operations allowed. /// Permission setting. /// Integer file handle, or -1 if the file could not be opened. /// /// To support reading from a cabinet that is concatenated onto /// another file, this function first searches for the offset of the cabinet, /// then saves that offset for use in recalculating later seeks. /// static FNOPEN(CabOpen) { UNREFERENCED_PARAMETER(pszFile); int hf; _wsopen__s(hf, g_szCabFile, oflag, _SH_DENYWR, pmode); if (hf != -1) { FDICABINETINFO cabInfo; int length = _lseek(hf, 0, SEEK_END); for(int offset = 0; offset < length; offset += 256) { if (_lseek(hf, offset, SEEK_SET) != offset) break; if (FDIIsCabinet(g_hfdi, hf, &cabInfo)) { g_lCabOffset = offset; _lseek(hf, offset, SEEK_SET); return hf; } } _close(hf); } return -1; } /// /// FDI callback to seek within a file. /// /// File handle. /// Seek distance /// Whether to seek relative to the /// beginning, current position, or end of the file. /// Resultant position within the cabinet. /// /// To support reading from a cabinet that is concatenated onto /// another file, this function recalculates seeks based on the /// offset that was determined when the cabinet was opened. /// static FNSEEK(CabSeek) { if (seektype == SEEK_SET) dist += g_lCabOffset; int pos = _lseek((int) hf, dist, seektype); pos -= g_lCabOffset; return pos; } /// /// Ensures a directory and its parent directory path exists. /// /// Directory path, not including file name. /// 0 if the directory exists or was successfully created, else nonzero. /// /// This function modifies characters in szDirPath, but always restores them /// regardless of error condition. /// static int EnsureDirectoryExists(__inout_z wchar_t* szDirPath) { int ret = 0; if (!::CreateDirectoryW(szDirPath, NULL)) { UINT err = ::GetLastError(); if (err != ERROR_ALREADY_EXISTS) { // Directory creation failed for some reason other than already existing. // Try to create the parent directory first. wchar_t* szLastSlash = NULL; for (wchar_t* sz = szDirPath; *sz; sz++) { if (*sz == L'\\') { szLastSlash = sz; } } if (szLastSlash) { // Temporarily take one directory off the path and recurse. *szLastSlash = L'\0'; ret = EnsureDirectoryExists(szDirPath); *szLastSlash = L'\\'; // Try to create the directory if all parents are created. if (ret == 0 && !::CreateDirectoryW(szDirPath, NULL)) { err = ::GetLastError(); if (err != ERROR_ALREADY_EXISTS) { ret = -1; } } } else { ret = -1; } } } return ret; } /// /// Ensures a file's directory and its parent directory path exists. /// /// Path including file name. /// 0 if the file's directory exists or was successfully created, else nonzero. /// /// This function modifies characters in szFilePath, but always restores them /// regardless of error condition. /// static int EnsureFileDirectoryExists(__inout_z wchar_t* szFilePath) { int ret = 0; wchar_t* szLastSlash = NULL; for (wchar_t* sz = szFilePath; *sz; sz++) { if (*sz == L'\\') { szLastSlash = sz; } } if (szLastSlash) { *szLastSlash = L'\0'; ret = EnsureDirectoryExists(szFilePath); *szLastSlash = L'\\'; } return ret; } /// /// FDI callback for handling files in the cabinet. /// /// Type of notification. /// Structure containing data about the notification. /// /// Refer to fdi.h for more comments on this notification callback. /// static FNFDINOTIFY(CabNotification) { // fdintCOPY_FILE: // Called for each file that *starts* in the current cabinet, giving // the client the opportunity to request that the file be copied or // skipped. // Entry: // pfdin->psz1 = file name in cabinet // pfdin->cb = uncompressed size of file // pfdin->date = file date // pfdin->time = file time // pfdin->attribs = file attributes // pfdin->iFolder = file's folder index // Exit-Success: // Return non-zero file handle for destination file; FDI writes // data to this file use the PFNWRITE function supplied to FDICreate, // and then calls fdintCLOSE_FILE_INFO to close the file and set // the date, time, and attributes. // Exit-Failure: // Returns 0 => Skip file, do not copy // Returns -1 => Abort FDICopy() call if (fdint == fdintCOPY_FILE) { size_t cchFile = MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, NULL, 0); size_t cchFilePath = wcslen(g_szExtractDir) + 1 + cchFile; wchar_t* szFilePath = (wchar_t*) _alloca((cchFilePath + 1) * sizeof(wchar_t)); if (szFilePath == NULL) return -1; StringCchCopyW(szFilePath, cchFilePath + 1, g_szExtractDir); StringCchCatW(szFilePath, cchFilePath + 1, L"\\"); MultiByteToWideChar(CP_UTF8, 0, pfdin->psz1, -1, szFilePath + cchFilePath - cchFile, (int) cchFile + 1); int hf = -1; if (EnsureFileDirectoryExists(szFilePath) == 0) { _wsopen__s(hf, szFilePath, _O_BINARY | _O_CREAT | _O_WRONLY | _O_SEQUENTIAL, _SH_DENYWR, _S_IREAD | _S_IWRITE); } return hf; } // fdintCLOSE_FILE_INFO: // Called after all of the data has been written to a target file. // This function must close the file and set the file date, time, // and attributes. // Entry: // pfdin->psz1 = file name in cabinet // pfdin->hf = file handle // pfdin->date = file date // pfdin->time = file time // pfdin->attribs = file attributes // pfdin->iFolder = file's folder index // pfdin->cb = Run After Extract (0 - don't run, 1 Run) // Exit-Success: // Returns TRUE // Exit-Failure: // Returns FALSE, or -1 to abort else if (fdint == fdintCLOSE_FILE_INFO) { _close((int) pfdin->hf); return TRUE; } return 0; } /// /// Extracts all contents of a cabinet file to a directory. /// /// Path to the cabinet file to be extracted. /// The cabinet may actually start at some offset within the file, /// as long as that offset is a multiple of 256. /// Directory where files are to be extracted. /// This directory must already exist, but should be empty. /// 0 if the cabinet was extracted successfully, /// or an error code if any error occurred. /// /// The extraction will not overwrite any files in the destination /// directory; extraction will be interrupted and fail if any files /// with the same name already exist. /// int ExtractCabinet(const wchar_t* szCabFile, const wchar_t* szExtractDir) { ERF erf; // Most of the FDI callbacks can be handled by existing CRT I/O functions. // For our functionality we only need to handle the open and seek callbacks. HFDI hfdi = FDICreate((PFNALLOC) malloc, (PFNFREE) free, CabOpen, (PFNREAD) _read, (PFNWRITE) _write, (PFNCLOSE) _close, CabSeek, cpu80386, &erf); if (hfdi != NULL) { g_hfdi = hfdi; g_szCabFile = szCabFile; g_szExtractDir = szExtractDir; char szEmpty[1] = {0}; if (FDICopy(hfdi, szEmpty, szEmpty, 0, CabNotification, NULL, NULL)) { FDIDestroy(hfdi); return 0; } FDIDestroy(hfdi); } return erf.erfOper; }