1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <windows.h> // NOLINT 6#include <fcntl.h> // for _O_* constants 7#include <fdi.h> 8 9#include "chrome/installer/mini_installer/decompress.h" 10 11namespace { 12 13FNALLOC(Alloc) { 14 return ::HeapAlloc(::GetProcessHeap(), 0, cb); 15} 16 17FNFREE(Free) { 18 ::HeapFree(::GetProcessHeap(), 0, pv); 19} 20 21// Converts a wide string to utf8. Set |len| to -1 if |str| is zero terminated 22// and you want to convert the entire string. 23// The returned string will have been allocated with Alloc(), so free it 24// with a call to Free(). 25char* WideToUtf8(const wchar_t* str, int len) { 26 char* ret = NULL; 27 int size = WideCharToMultiByte(CP_UTF8, 0, str, len, NULL, 0, NULL, NULL); 28 if (size) { 29 if (len != -1) 30 ++size; // include space for the terminator. 31 ret = reinterpret_cast<char*>(Alloc(size * sizeof(ret[0]))); 32 if (ret) { 33 WideCharToMultiByte(CP_UTF8, 0, str, len, ret, size, NULL, NULL); 34 if (len != -1) 35 ret[size - 1] = '\0'; // terminate the string 36 } 37 } 38 return ret; 39} 40 41wchar_t* Utf8ToWide(const char* str) { 42 wchar_t* ret = NULL; 43 int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); 44 if (size) { 45 ret = reinterpret_cast<wchar_t*>(Alloc(size * sizeof(ret[0]))); 46 if (ret) 47 MultiByteToWideChar(CP_UTF8, 0, str, -1, ret, size); 48 } 49 return ret; 50} 51 52template <typename T> 53class scoped_ptr { 54 public: 55 explicit scoped_ptr(T* a) : a_(a) { 56 } 57 ~scoped_ptr() { 58 if (a_) 59 Free(a_); 60 } 61 operator T*() { 62 return a_; 63 } 64 private: 65 T* a_; 66}; 67 68FNOPEN(Open) { 69 DWORD access = 0; 70 DWORD disposition = 0; 71 72 if (oflag & _O_RDWR) { 73 access = GENERIC_READ | GENERIC_WRITE; 74 } else if (oflag & _O_WRONLY) { 75 access = GENERIC_WRITE; 76 } else { 77 access = GENERIC_READ; 78 } 79 80 if (oflag & _O_CREAT) { 81 disposition = CREATE_ALWAYS; 82 } else { 83 disposition = OPEN_EXISTING; 84 } 85 86 scoped_ptr<wchar_t> path(Utf8ToWide(pszFile)); 87 HANDLE file = CreateFileW(path, access, FILE_SHARE_READ, NULL, disposition, 88 FILE_ATTRIBUTE_NORMAL, NULL); 89 return reinterpret_cast<INT_PTR>(file); 90} 91 92FNREAD(Read) { 93 DWORD read = 0; 94 if (!::ReadFile(reinterpret_cast<HANDLE>(hf), pv, cb, &read, NULL)) 95 read = static_cast<DWORD>(-1L); 96 return read; 97} 98 99FNWRITE(Write) { 100 DWORD written = 0; 101 if (!::WriteFile(reinterpret_cast<HANDLE>(hf), pv, cb, &written, NULL)) 102 written = static_cast<DWORD>(-1L); 103 return written; 104} 105 106FNCLOSE(Close) { 107 return ::CloseHandle(reinterpret_cast<HANDLE>(hf)) ? 0 : -1; 108} 109 110FNSEEK(Seek) { 111 return ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, NULL, seektype); 112} 113 114FNFDINOTIFY(Notify) { 115 INT_PTR result = 0; 116 117 // Since we will only ever be decompressing a single file at a time 118 // we take a shortcut and provide a pointer to the wide destination file 119 // of the file we want to write. This way we don't have to bother with 120 // utf8/wide conversion and concatenation of directory and file name. 121 const wchar_t* destination = reinterpret_cast<const wchar_t*>(pfdin->pv); 122 123 switch (fdint) { 124 case fdintCOPY_FILE: { 125 result = reinterpret_cast<INT_PTR>(::CreateFileW(destination, 126 GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 127 FILE_ATTRIBUTE_NORMAL, NULL)); 128 break; 129 } 130 131 case fdintCLOSE_FILE_INFO: { 132 FILETIME file_time; 133 FILETIME local; 134 // Converts MS-DOS date and time values to a file time 135 if (DosDateTimeToFileTime(pfdin->date, pfdin->time, &file_time) && 136 LocalFileTimeToFileTime(&file_time, &local)) { 137 SetFileTime(reinterpret_cast<HANDLE>(pfdin->hf), &local, NULL, NULL); 138 } 139 140 result = !Close(pfdin->hf); 141 pfdin->attribs &= FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | 142 FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE; 143 ::SetFileAttributes(destination, pfdin->attribs); 144 break; 145 } 146 147 case fdintCABINET_INFO: 148 case fdintENUMERATE: 149 // OK. continue as normal. 150 result = 0; 151 break; 152 153 case fdintPARTIAL_FILE: 154 case fdintNEXT_CABINET: 155 default: 156 // Error case. 157 result = -1; 158 break; 159 } 160 161 return result; 162} 163 164// Module handle of cabinet.dll 165HMODULE g_fdi = NULL; 166 167// API prototypes. 168typedef HFDI (DIAMONDAPI* FDICreateFn)(PFNALLOC alloc, PFNFREE free, 169 PFNOPEN open, PFNREAD read, 170 PFNWRITE write, PFNCLOSE close, 171 PFNSEEK seek, int cpu_type, PERF perf); 172typedef BOOL (DIAMONDAPI* FDIDestroyFn)(HFDI fdi); 173typedef BOOL (DIAMONDAPI* FDICopyFn)(HFDI fdi, char* cab, char* cab_path, 174 int flags, PFNFDINOTIFY notify, 175 PFNFDIDECRYPT decrypt, void* context); 176FDICreateFn g_FDICreate = NULL; 177FDIDestroyFn g_FDIDestroy = NULL; 178FDICopyFn g_FDICopy = NULL; 179 180bool InitializeFdi() { 181 if (!g_fdi) { 182 // It has been observed that some users do not have the expected 183 // environment variables set, so we try a couple that *should* always be 184 // present and fallback to the default Windows install path if all else 185 // fails. 186 // The cabinet.dll should be available on all supported versions of Windows. 187 static const wchar_t* const candidate_paths[] = { 188 L"%WINDIR%\\system32\\cabinet.dll", 189 L"%SYSTEMROOT%\\system32\\cabinet.dll", 190 L"C:\\Windows\\system32\\cabinet.dll", 191 }; 192 193 wchar_t path[MAX_PATH] = {0}; 194 for (int i = 0; i < arraysize(candidate_paths); ++i) { 195 path[0] = L'\0'; 196 DWORD result = ::ExpandEnvironmentStringsW(candidate_paths[i], 197 path, arraysize(path)); 198 199 if (result > 0 && result <= arraysize(path)) 200 g_fdi = ::LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); 201 202 if (g_fdi) 203 break; 204 } 205 } 206 207 if (g_fdi) { 208 g_FDICreate = 209 reinterpret_cast<FDICreateFn>(::GetProcAddress(g_fdi, "FDICreate")); 210 g_FDIDestroy = 211 reinterpret_cast<FDIDestroyFn>(::GetProcAddress(g_fdi, "FDIDestroy")); 212 g_FDICopy = 213 reinterpret_cast<FDICopyFn>(::GetProcAddress(g_fdi, "FDICopy")); 214 } 215 216 return g_FDICreate && g_FDIDestroy && g_FDICopy; 217} 218 219} // namespace 220 221namespace mini_installer { 222 223bool Expand(const wchar_t* source, const wchar_t* destination) { 224 if (!InitializeFdi()) 225 return false; 226 227 // Start by splitting up the source path and convert to utf8 since the 228 // cabinet API doesn't support wide strings. 229 const wchar_t* source_name = source + lstrlenW(source); 230 while (source_name > source && *source_name != L'\\') 231 --source_name; 232 if (source_name == source) 233 return false; 234 235 // Convert the name to utf8. 236 source_name++; 237 scoped_ptr<char> source_name_utf8(WideToUtf8(source_name, -1)); 238 // The directory part is assumed to have a trailing backslash. 239 scoped_ptr<char> source_path_utf8(WideToUtf8(source, source_name - source)); 240 241 scoped_ptr<char> dest_utf8(WideToUtf8(destination, -1)); 242 if (!dest_utf8 || !source_name_utf8 || !source_path_utf8) 243 return false; 244 245 bool success = false; 246 247 ERF erf = {0}; 248 HFDI fdi = g_FDICreate(&Alloc, &Free, &Open, &Read, &Write, &Close, &Seek, 249 cpuUNKNOWN, &erf); 250 if (fdi) { 251 if (g_FDICopy(fdi, source_name_utf8, source_path_utf8, 0, 252 &Notify, NULL, const_cast<wchar_t*>(destination))) { 253 success = true; 254 } 255 g_FDIDestroy(fdi); 256 } 257 258 return success; 259} 260 261} // namespace mini_installer 262