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