| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/installer/mini_installer/decompress.h" |
| |
| #include <windows.h> |
| |
| #include <fcntl.h> // for _O_* constants |
| #include <fdi.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| |
| #include "chrome/installer/mini_installer/mini_file.h" |
| |
| namespace { |
| |
| // A simple struct to hold data passed to and from FDICopy via its |pvUser| |
| // argument. |
| struct ExpandContext { |
| // The path to the single destination file. |
| const wchar_t* const dest_path; |
| |
| // The destination file; valid once the destination is created. |
| mini_installer::MiniFile dest_file; |
| |
| // Set to true if the file was extracted to |dest_path|. Note that |dest_file| |
| // may be valid even in case of failure. |
| bool succeeded; |
| }; |
| |
| FNALLOC(Alloc) { |
| return ::HeapAlloc(::GetProcessHeap(), 0, cb); |
| } |
| |
| FNFREE(Free) { |
| ::HeapFree(::GetProcessHeap(), 0, pv); |
| } |
| |
| // Converts a wide string to utf8. Set |len| to -1 if |str| is zero terminated |
| // and you want to convert the entire string. |
| // The returned string will have been allocated with Alloc(), so free it |
| // with a call to Free(). |
| char* WideToUtf8(const wchar_t* str, int len) { |
| char* ret = nullptr; |
| int size = |
| WideCharToMultiByte(CP_UTF8, 0, str, len, nullptr, 0, nullptr, nullptr); |
| if (size) { |
| if (len != -1) |
| ++size; // include space for the terminator. |
| ret = reinterpret_cast<char*>(Alloc(size * sizeof(ret[0]))); |
| if (ret) { |
| WideCharToMultiByte(CP_UTF8, 0, str, len, ret, size, nullptr, nullptr); |
| if (len != -1) |
| ret[size - 1] = '\0'; // terminate the string |
| } |
| } |
| return ret; |
| } |
| |
| wchar_t* Utf8ToWide(const char* str) { |
| wchar_t* ret = nullptr; |
| int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); |
| if (size) { |
| ret = reinterpret_cast<wchar_t*>(Alloc(size * sizeof(ret[0]))); |
| if (ret) |
| MultiByteToWideChar(CP_UTF8, 0, str, -1, ret, size); |
| } |
| return ret; |
| } |
| |
| template <typename T> |
| class scoped_ptr { |
| public: |
| explicit scoped_ptr(T* a) : a_(a) {} |
| ~scoped_ptr() { |
| if (a_) |
| Free(a_); |
| } |
| operator T*() { return a_; } |
| |
| private: |
| T* a_; |
| }; |
| |
| FNOPEN(Open) { |
| DWORD access = 0; |
| DWORD disposition = 0; |
| |
| if (oflag & _O_RDWR) { |
| access = GENERIC_READ | GENERIC_WRITE; |
| } else if (oflag & _O_WRONLY) { |
| access = GENERIC_WRITE; |
| } else { |
| access = GENERIC_READ; |
| } |
| |
| if (oflag & _O_CREAT) { |
| disposition = CREATE_ALWAYS; |
| } else { |
| disposition = OPEN_EXISTING; |
| } |
| |
| scoped_ptr<wchar_t> path(Utf8ToWide(pszFile)); |
| HANDLE file = |
| CreateFileW(path, access, FILE_SHARE_DELETE | FILE_SHARE_READ, nullptr, |
| disposition, FILE_ATTRIBUTE_NORMAL, nullptr); |
| return reinterpret_cast<INT_PTR>(file); |
| } |
| |
| FNREAD(Read) { |
| DWORD read = 0; |
| if (!::ReadFile(reinterpret_cast<HANDLE>(hf), pv, cb, &read, nullptr)) |
| read = static_cast<DWORD>(-1L); |
| return read; |
| } |
| |
| FNWRITE(Write) { |
| DWORD written = 0; |
| if (!::WriteFile(reinterpret_cast<HANDLE>(hf), pv, cb, &written, nullptr)) |
| written = static_cast<DWORD>(-1L); |
| return written; |
| } |
| |
| FNCLOSE(Close) { |
| return ::CloseHandle(reinterpret_cast<HANDLE>(hf)) ? 0 : -1; |
| } |
| |
| FNSEEK(Seek) { |
| return ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, nullptr, |
| seektype); |
| } |
| |
| FNFDINOTIFY(Notify) { |
| // Since we will only ever be decompressing a single file at a time |
| // we take a shortcut and provide a pointer to the wide destination file |
| // of the file we want to write. This way we don't have to bother with |
| // utf8/wide conversion and concatenation of directory and file name. |
| ExpandContext& context = *reinterpret_cast<ExpandContext*>(pfdin->pv); |
| |
| switch (fdint) { |
| case fdintCOPY_FILE: |
| context.dest_file.Create(context.dest_path); |
| // By sheer coincidence, CreateFileW's success/failure results match that |
| // of fdintCOPY_FILE. The handle given out here is closed either by |
| // FDICopy (in case of error) or below when handling fdintCLOSE_FILE_INFO |
| // (in case of success). |
| return reinterpret_cast<INT_PTR>(context.dest_file.DuplicateHandle()); |
| |
| case fdintCLOSE_FILE_INFO: { |
| // Set the file's creation time and file attributes. |
| FILE_BASIC_INFO info = {}; |
| FILETIME file_time; |
| FILETIME local; |
| if (DosDateTimeToFileTime(pfdin->date, pfdin->time, &file_time) && |
| LocalFileTimeToFileTime(&file_time, &local)) { |
| info.CreationTime.u.LowPart = local.dwLowDateTime; |
| info.CreationTime.u.HighPart = local.dwHighDateTime; |
| } |
| info.FileAttributes = |
| pfdin->attribs & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | |
| FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE); |
| ::SetFileInformationByHandle(reinterpret_cast<HANDLE>(pfdin->hf), |
| FileBasicInfo, &info, sizeof(info)); |
| |
| // Close the handle given out above in fdintCOPY_FILE. |
| ::CloseHandle(reinterpret_cast<HANDLE>(pfdin->hf)); |
| context.succeeded = true; |
| return -1; // Break: the one file was extracted. |
| } |
| |
| case fdintCABINET_INFO: |
| case fdintENUMERATE: |
| return 0; // Continue: success. |
| |
| case fdintPARTIAL_FILE: |
| case fdintNEXT_CABINET: |
| default: |
| return -1; // Break: error. |
| } |
| } |
| |
| // Module handle of cabinet.dll |
| HMODULE g_fdi = nullptr; |
| |
| // API prototypes. |
| typedef HFDI(DIAMONDAPI* FDICreateFn)(PFNALLOC alloc, |
| PFNFREE free, |
| PFNOPEN open, |
| PFNREAD read, |
| PFNWRITE write, |
| PFNCLOSE close, |
| PFNSEEK seek, |
| int cpu_type, |
| PERF perf); |
| typedef BOOL(DIAMONDAPI* FDIDestroyFn)(HFDI fdi); |
| typedef BOOL(DIAMONDAPI* FDICopyFn)(HFDI fdi, |
| char* cab, |
| char* cab_path, |
| int flags, |
| PFNFDINOTIFY notify, |
| PFNFDIDECRYPT decrypt, |
| void* context); |
| FDICreateFn g_FDICreate = nullptr; |
| FDIDestroyFn g_FDIDestroy = nullptr; |
| FDICopyFn g_FDICopy = nullptr; |
| |
| bool InitializeFdi() { |
| if (!g_fdi) { |
| // It has been observed that some users do not have the expected |
| // environment variables set, so we try a couple that *should* always be |
| // present and fallback to the default Windows install path if all else |
| // fails. |
| // The cabinet.dll should be available on all supported versions of Windows. |
| static const wchar_t* const candidate_paths[] = { |
| L"%WINDIR%\\system32\\cabinet.dll", |
| L"%SYSTEMROOT%\\system32\\cabinet.dll", |
| L"C:\\Windows\\system32\\cabinet.dll", |
| }; |
| |
| wchar_t path[MAX_PATH] = {0}; |
| for (size_t i = 0; i < _countof(candidate_paths); ++i) { |
| path[0] = L'\0'; |
| DWORD result = |
| ::ExpandEnvironmentStringsW(candidate_paths[i], path, _countof(path)); |
| |
| if (result > 0 && result <= _countof(path)) |
| g_fdi = ::LoadLibraryExW(path, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); |
| |
| if (g_fdi) |
| break; |
| } |
| } |
| |
| if (g_fdi) { |
| g_FDICreate = |
| reinterpret_cast<FDICreateFn>(::GetProcAddress(g_fdi, "FDICreate")); |
| g_FDIDestroy = |
| reinterpret_cast<FDIDestroyFn>(::GetProcAddress(g_fdi, "FDIDestroy")); |
| g_FDICopy = reinterpret_cast<FDICopyFn>(::GetProcAddress(g_fdi, "FDICopy")); |
| } |
| |
| return g_FDICreate && g_FDIDestroy && g_FDICopy; |
| } |
| |
| } // namespace |
| |
| namespace mini_installer { |
| |
| bool Expand(const wchar_t* source, const wchar_t* destination) { |
| if (!InitializeFdi()) |
| return false; |
| |
| // Start by splitting up the source path and convert to utf8 since the |
| // cabinet API doesn't support wide strings. |
| const wchar_t* source_name = source + lstrlenW(source); |
| while (source_name > source && *source_name != L'\\') |
| --source_name; |
| if (source_name == source) |
| return false; |
| |
| // Convert the name to utf8. |
| source_name++; |
| scoped_ptr<char> source_name_utf8(WideToUtf8(source_name, -1)); |
| // The directory part is assumed to have a trailing backslash. |
| scoped_ptr<char> source_path_utf8(WideToUtf8(source, source_name - source)); |
| |
| if (!source_name_utf8 || !source_path_utf8) |
| return false; |
| |
| ERF erf = {0}; |
| HFDI fdi = g_FDICreate(&Alloc, &Free, &Open, &Read, &Write, &Close, &Seek, |
| cpuUNKNOWN, &erf); |
| if (!fdi) |
| return false; |
| |
| ExpandContext context = {destination, {}, /*succeeded=*/false}; |
| g_FDICopy(fdi, source_name_utf8, source_path_utf8, 0, &Notify, nullptr, |
| &context); |
| g_FDIDestroy(fdi); |
| if (context.succeeded) |
| return true; |
| |
| // Delete the output file if it was created. |
| if (context.dest_file.IsValid()) |
| context.dest_file.DeleteOnClose(); |
| |
| return false; |
| } |
| |
| } // namespace mini_installer |