Home | History | Annotate | Download | only in mini_installer
      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 
     13 FNALLOC(Alloc) {
     14   return ::HeapAlloc(::GetProcessHeap(), 0, cb);
     15 }
     16 
     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().
     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 
     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:
     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 
     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 
     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 
     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 
    106 FNCLOSE(Close) {
    107   return ::CloseHandle(reinterpret_cast<HANDLE>(hf)) ? 0 : -1;
    108 }
    109 
    110 FNSEEK(Seek) {
    111   return ::SetFilePointer(reinterpret_cast<HANDLE>(hf), dist, NULL, seektype);
    112 }
    113 
    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 
    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 
    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