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
11 namespace {
12
FNALLOC(Alloc)13 FNALLOC(Alloc) {
14 return ::HeapAlloc(::GetProcessHeap(), 0, cb);
15 }
16
FNFREE(Free)17 FNFREE(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().
WideToUtf8(const wchar_t * str,int len)25 char* 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
Utf8ToWide(const char * str)41 wchar_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
52 template <typename T>
53 class scoped_ptr {
54 public:
scoped_ptr(T * a)55 explicit scoped_ptr(T* a) : a_(a) {
56 }
~scoped_ptr()57 ~scoped_ptr() {
58 if (a_)
59 Free(a_);
60 }
operator T*()61 operator T*() {
62 return a_;
63 }
64 private:
65 T* a_;
66 };
67
FNOPEN(Open)68 FNOPEN(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
FNREAD(Read)92 FNREAD(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
FNWRITE(Write)99 FNWRITE(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
FNCLOSE(Close)106 FNCLOSE(Close) {
107 return ::CloseHandle(reinterpret_cast<HANDLE>(hf)) ? 0 : -1;
108 }
109
FNSEEK(Seek)110 FNSEEK(Seek) {
111 return ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, NULL, seektype);
112 }
113
FNFDINOTIFY(Notify)114 FNFDINOTIFY(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
165 HMODULE g_fdi = NULL;
166
167 // API prototypes.
168 typedef 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);
172 typedef BOOL (DIAMONDAPI* FDIDestroyFn)(HFDI fdi);
173 typedef BOOL (DIAMONDAPI* FDICopyFn)(HFDI fdi, char* cab, char* cab_path,
174 int flags, PFNFDINOTIFY notify,
175 PFNFDIDECRYPT decrypt, void* context);
176 FDICreateFn g_FDICreate = NULL;
177 FDIDestroyFn g_FDIDestroy = NULL;
178 FDICopyFn g_FDICopy = NULL;
179
InitializeFdi()180 bool 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
221 namespace mini_installer {
222
Expand(const wchar_t * source,const wchar_t * destination)223 bool 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