diff options
-rw-r--r-- | chrome/chrome_installer.gypi | 3 | ||||
-rw-r--r-- | chrome/installer/mini_installer.gyp | 4 | ||||
-rw-r--r-- | chrome/installer/mini_installer/decompress.cc | 240 | ||||
-rw-r--r-- | chrome/installer/mini_installer/decompress.h | 19 | ||||
-rw-r--r-- | chrome/installer/mini_installer/decompress_test.cc | 44 | ||||
-rw-r--r-- | chrome/installer/mini_installer/mini_installer.cc | 23 | ||||
-rw-r--r-- | chrome/installer/test/data/SETUP.EX_ | bin | 0 -> 908964 bytes |
7 files changed, 319 insertions, 14 deletions
diff --git a/chrome/chrome_installer.gypi b/chrome/chrome_installer.gypi index 1e41e30..05f287f 100644 --- a/chrome/chrome_installer.gypi +++ b/chrome/chrome_installer.gypi @@ -341,6 +341,9 @@ # below into a separate lib and then link both setup.exe and # setup_unittests.exe against that. 'sources': [ + 'installer/mini_installer/decompress.cc', + 'installer/mini_installer/decompress.h', + 'installer/mini_installer/decompress_test.cc', 'installer/mini_installer/mini_string.cc', 'installer/mini_installer/mini_string.h', 'installer/mini_installer/mini_string_test.cc', diff --git a/chrome/installer/mini_installer.gyp b/chrome/installer/mini_installer.gyp index 7ab32c6..84a2f70 100644 --- a/chrome/installer/mini_installer.gyp +++ b/chrome/installer/mini_installer.gyp @@ -25,6 +25,8 @@ ], 'sources': [ 'mini_installer/appid.h', + 'mini_installer/decompress.cc', + 'mini_installer/decompress.h', 'mini_installer/mini_installer.cc', 'mini_installer/mini_installer.h', 'mini_installer/mini_installer.ico', @@ -67,13 +69,11 @@ 'crt\\src\\intel\\mt_lib\\memset.obj"', '"$(VCInstallDir)..\\..\\Microsoft Visual Studio 9.0\\VC\\' 'crt\\src\\intel\\mt_lib\\P4_memset.obj"', - 'setupapi.lib', ], },{ 'AdditionalDependencies': [ '"$(VCInstallDir)crt\\src\\intel\\mt_lib\\memset.obj"', '"$(VCInstallDir)crt\\src\\intel\\mt_lib\\P4_memset.obj"', - 'setupapi.lib', ], }], ], diff --git a/chrome/installer/mini_installer/decompress.cc b/chrome/installer/mini_installer/decompress.cc new file mode 100644 index 0000000..ced226d --- /dev/null +++ b/chrome/installer/mini_installer/decompress.cc @@ -0,0 +1,240 @@ +// 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 <windows.h> // NOLINT +#include <fcntl.h> // for _O_* constants +#include <fdi.h> + +#include "chrome/installer/mini_installer/decompress.h" + +namespace { + +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 = NULL; + int size = WideCharToMultiByte(CP_UTF8, 0, str, len, NULL, 0, NULL, NULL); + 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, NULL, NULL); + if (len != -1) + ret[size - 1] = '\0'; // terminate the string + } + } + return ret; +} + +wchar_t* Utf8ToWide(const char* str) { + wchar_t* ret = NULL; + int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 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_READ, NULL, disposition, + FILE_ATTRIBUTE_NORMAL, NULL); + return reinterpret_cast<INT_PTR>(file); +} + +FNREAD(Read) { + DWORD read = 0; + if (!::ReadFile(reinterpret_cast<HANDLE>(hf), pv, cb, &read, NULL)) + read = static_cast<DWORD>(-1L); + return read; +} + +FNWRITE(Write) { + DWORD written = 0; + if (!::WriteFile(reinterpret_cast<HANDLE>(hf), pv, cb, &written, NULL)) + 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, NULL, seektype); +} + +FNFDINOTIFY(Notify) { + INT_PTR result = 0; + + // 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. + const wchar_t* destination = reinterpret_cast<const wchar_t*>(pfdin->pv); + + switch (fdint) { + case fdintCOPY_FILE: { + result = reinterpret_cast<INT_PTR>(::CreateFileW(destination, + GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL)); + break; + } + + case fdintCLOSE_FILE_INFO: { + FILETIME file_time; + FILETIME local; + // Converts MS-DOS date and time values to a file time + if (DosDateTimeToFileTime(pfdin->date, pfdin->time, &file_time) && + LocalFileTimeToFileTime(&file_time, &local)) { + SetFileTime(reinterpret_cast<HANDLE>(pfdin->hf), &local, NULL, NULL); + } + + result = !Close(pfdin->hf); + pfdin->attribs &= FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE; + ::SetFileAttributes(destination, pfdin->attribs); + break; + } + + case fdintCABINET_INFO: + case fdintENUMERATE: + // OK. continue as normal. + result = 0; + break; + + case fdintPARTIAL_FILE: + case fdintNEXT_CABINET: + default: + // Error case. + result = -1; + break; + } + + return result; +} + +// Module handle of cabinet.dll +HMODULE g_fdi = NULL; + +// 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 = NULL; +FDIDestroyFn g_FDIDestroy = NULL; +FDICopyFn g_FDICopy = NULL; + +bool InitializeFdi() { + if (!g_fdi) { + wchar_t path[MAX_PATH] = {0}; + ::ExpandEnvironmentStringsW(L"%WINDIR%\\system32\\cabinet.dll", path, + MAX_PATH); + // This DLL should be available on all supported versions of Windows. + g_fdi = ::LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + 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)); + + scoped_ptr<char> dest_utf8(WideToUtf8(destination, -1)); + if (!dest_utf8 || !source_name_utf8 || !source_path_utf8) + return false; + + bool success = false; + + ERF erf = {0}; + HFDI fdi = g_FDICreate(&Alloc, &Free, &Open, &Read, &Write, &Close, &Seek, + cpuUNKNOWN, &erf); + if (fdi) { + if (g_FDICopy(fdi, source_name_utf8, source_path_utf8, 0, + &Notify, NULL, const_cast<wchar_t*>(destination))) { + success = true; + } + g_FDIDestroy(fdi); + } + + return success; +} + +} // namespace mini_installer diff --git a/chrome/installer/mini_installer/decompress.h b/chrome/installer/mini_installer/decompress.h new file mode 100644 index 0000000..8e093aa --- /dev/null +++ b/chrome/installer/mini_installer/decompress.h @@ -0,0 +1,19 @@ +// 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. + +#ifndef CHROME_INSTALLER_MINI_INSTALLER_DECOMPRESS_H_ +#define CHROME_INSTALLER_MINI_INSTALLER_DECOMPRESS_H_ +#pragma once + +namespace mini_installer { + +// Same as the tool, expand.exe. Decompresses a file that was compressed +// using Microsoft's MSCF compression algorithm. +// |source| is the full path of the file to decompress and |destination| +// is the full path of the target file. +bool Expand(const wchar_t* source, const wchar_t* destination); + +} // namespace mini_installer + +#endif // CHROME_INSTALLER_MINI_INSTALLER_DECOMPRESS_H_ diff --git a/chrome/installer/mini_installer/decompress_test.cc b/chrome/installer/mini_installer/decompress_test.cc new file mode 100644 index 0000000..9258412 --- /dev/null +++ b/chrome/installer/mini_installer/decompress_test.cc @@ -0,0 +1,44 @@ +// 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 <windows.h> + +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/scoped_temp_dir.h" +#include "chrome/installer/mini_installer/decompress.h" +#include "testing/gtest/include/gtest/gtest.h" + +class MiniDecompressTest : public testing::Test { + protected: + virtual void SetUp() { + } + virtual void TearDown() { + } +}; + +TEST_F(MiniDecompressTest, ExpandTest) { + FilePath source_path; + PathService::Get(base::DIR_SOURCE_ROOT, &source_path); + source_path = source_path.Append(FILE_PATH_LITERAL("chrome")) + .Append(FILE_PATH_LITERAL("installer")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("SETUP.EX_")); + + // Prepare a temp folder that will be automatically deleted along with + // our temporary test data. + ScopedTempDir temp_dir; + EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); + FilePath dest_path(temp_dir.path().Append(FILE_PATH_LITERAL("setup.exe"))); + + // Decompress our test file. + EXPECT_TRUE(mini_installer::Expand(source_path.value().c_str(), + dest_path.value().c_str())); + + // Check if the expanded file is a valid executable. + DWORD type = static_cast<DWORD>(-1); + EXPECT_TRUE(GetBinaryType(dest_path.value().c_str(), &type)); + EXPECT_EQ(SCS_32BIT_BINARY, type); +} diff --git a/chrome/installer/mini_installer/mini_installer.cc b/chrome/installer/mini_installer/mini_installer.cc index a06fa9a..d9ed606 100644 --- a/chrome/installer/mini_installer/mini_installer.cc +++ b/chrome/installer/mini_installer/mini_installer.cc @@ -33,6 +33,7 @@ #include <shellapi.h> #include "chrome/installer/mini_installer/appid.h" +#include "chrome/installer/mini_installer/decompress.h" #include "chrome/installer/mini_installer/mini_installer.h" #include "chrome/installer/mini_installer/mini_string.h" #include "chrome/installer/mini_installer/pe_resource.h" @@ -427,20 +428,18 @@ bool UnpackBinaryResources(HMODULE module, const wchar_t* base_path, if (setup_path->length() > 0) { // Uncompress LZ compressed resource. Setup is packed with 'MSCF' - // as opposed to old DOS way of 'SZDD'. Hence use SetupInstallFile - // instead of LZCopy. - // Note that the API will automatically delete the original file - // if the extraction was successful. - // TODO(tommi): Use the cabinet API directly. - if (!SetupInstallFile(NULL, NULL, setup_path->get(), NULL, - setup_dest_path.get(), - SP_COPY_DELETESOURCE | SP_COPY_SOURCE_ABSOLUTE, - NULL, NULL)) { - DeleteFile(setup_path->get()); - return false; + // as opposed to old DOS way of 'SZDD'. Hence we don't use LZCopy. + bool success = mini_installer::Expand(setup_path->get(), + setup_dest_path.get()); + ::DeleteFile(setup_path->get()); + if (success) { + if (!setup_path->assign(setup_dest_path.get())) { + ::DeleteFile(setup_dest_path.get()); + success = false; + } } - return setup_path->assign(setup_dest_path.get()); + return success; } // setup.exe still not found. So finally check if it was sent as 'BN' diff --git a/chrome/installer/test/data/SETUP.EX_ b/chrome/installer/test/data/SETUP.EX_ Binary files differnew file mode 100644 index 0000000..976ffcf --- /dev/null +++ b/chrome/installer/test/data/SETUP.EX_ |