Home | History | Annotate | Download | only in SfxSetup
      1 /* SfxSetup.c - 7z SFX Setup
      2 2014-12-07 : Igor Pavlov : Public domain */
      3 
      4 #include "Precomp.h"
      5 
      6 #ifndef UNICODE
      7 #define UNICODE
      8 #endif
      9 
     10 #ifndef _UNICODE
     11 #define _UNICODE
     12 #endif
     13 
     14 #ifdef _CONSOLE
     15 #include <stdio.h>
     16 #endif
     17 
     18 #include "../../7z.h"
     19 #include "../../7zAlloc.h"
     20 #include "../../7zCrc.h"
     21 #include "../../7zFile.h"
     22 #include "../../CpuArch.h"
     23 
     24 #define k_EXE_ExtIndex 2
     25 
     26 static const char *kExts[] =
     27 {
     28     "bat"
     29   , "cmd"
     30   , "exe"
     31   , "inf"
     32   , "msi"
     33   #ifdef UNDER_CE
     34   , "cab"
     35   #endif
     36   , "html"
     37   , "htm"
     38 };
     39 
     40 static const char *kNames[] =
     41 {
     42     "setup"
     43   , "install"
     44   , "run"
     45   , "start"
     46 };
     47 
     48 static unsigned FindExt(const wchar_t *s, unsigned *extLen)
     49 {
     50   unsigned len = (unsigned)wcslen(s);
     51   unsigned i;
     52   for (i = len; i > 0; i--)
     53   {
     54     if (s[i - 1] == '.')
     55     {
     56       *extLen = len - i;
     57       return i - 1;
     58     }
     59   }
     60   *extLen = 0;
     61   return len;
     62 }
     63 
     64 #define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) -= 0x20 : (c)))
     65 
     66 static unsigned FindItem(const char **items, unsigned num, const wchar_t *s, unsigned len)
     67 {
     68   unsigned i;
     69   for (i = 0; i < num; i++)
     70   {
     71     const char *item = items[i];
     72     unsigned itemLen = (unsigned)strlen(item);
     73     unsigned j;
     74     if (len != itemLen)
     75       continue;
     76     for (j = 0; j < len; j++)
     77     {
     78       unsigned c = item[j];
     79       if (c != s[j] && MAKE_CHAR_UPPER(c) != s[j])
     80         break;
     81     }
     82     if (j == len)
     83       return i;
     84   }
     85   return i;
     86 }
     87 
     88 #ifdef _CONSOLE
     89 static BOOL WINAPI HandlerRoutine(DWORD ctrlType)
     90 {
     91   ctrlType = ctrlType;
     92   return TRUE;
     93 }
     94 #endif
     95 
     96 static void PrintErrorMessage(const char *message)
     97 {
     98   #ifdef _CONSOLE
     99   printf("\n7-Zip Error: %s\n", message);
    100   #else
    101   #ifdef UNDER_CE
    102   WCHAR messageW[256 + 4];
    103   unsigned i;
    104   for (i = 0; i < 256 && message[i] != 0; i++)
    105     messageW[i] = message[i];
    106   messageW[i] = 0;
    107   MessageBoxW(0, messageW, L"7-Zip Error", MB_ICONERROR);
    108   #else
    109   MessageBoxA(0, message, "7-Zip Error", MB_ICONERROR);
    110   #endif
    111   #endif
    112 }
    113 
    114 static WRes MyCreateDir(const WCHAR *name)
    115 {
    116   return CreateDirectoryW(name, NULL) ? 0 : GetLastError();
    117 }
    118 
    119 #ifdef UNDER_CE
    120 #define kBufferSize (1 << 13)
    121 #else
    122 #define kBufferSize (1 << 15)
    123 #endif
    124 
    125 #define kSignatureSearchLimit (1 << 22)
    126 
    127 static Bool FindSignature(CSzFile *stream, UInt64 *resPos)
    128 {
    129   Byte buf[kBufferSize];
    130   size_t numPrevBytes = 0;
    131   *resPos = 0;
    132   for (;;)
    133   {
    134     size_t processed, pos;
    135     if (*resPos > kSignatureSearchLimit)
    136       return False;
    137     processed = kBufferSize - numPrevBytes;
    138     if (File_Read(stream, buf + numPrevBytes, &processed) != 0)
    139       return False;
    140     processed += numPrevBytes;
    141     if (processed < k7zStartHeaderSize ||
    142         (processed == k7zStartHeaderSize && numPrevBytes != 0))
    143       return False;
    144     processed -= k7zStartHeaderSize;
    145     for (pos = 0; pos <= processed; pos++)
    146     {
    147       for (; buf[pos] != '7' && pos <= processed; pos++);
    148       if (pos > processed)
    149         break;
    150       if (memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0)
    151         if (CrcCalc(buf + pos + 12, 20) == GetUi32(buf + pos + 8))
    152         {
    153           *resPos += pos;
    154           return True;
    155         }
    156     }
    157     *resPos += processed;
    158     numPrevBytes = k7zStartHeaderSize;
    159     memmove(buf, buf + processed, k7zStartHeaderSize);
    160   }
    161 }
    162 
    163 static Bool DoesFileOrDirExist(const WCHAR *path)
    164 {
    165   WIN32_FIND_DATAW fd;
    166   HANDLE handle;
    167   handle = FindFirstFileW(path, &fd);
    168   if (handle == INVALID_HANDLE_VALUE)
    169     return False;
    170   FindClose(handle);
    171   return True;
    172 }
    173 
    174 static WRes RemoveDirWithSubItems(WCHAR *path)
    175 {
    176   WIN32_FIND_DATAW fd;
    177   HANDLE handle;
    178   WRes res = 0;
    179   size_t len = wcslen(path);
    180   wcscpy(path + len, L"*");
    181   handle = FindFirstFileW(path, &fd);
    182   path[len] = L'\0';
    183   if (handle == INVALID_HANDLE_VALUE)
    184     return GetLastError();
    185   for (;;)
    186   {
    187     if (wcscmp(fd.cFileName, L".") != 0 &&
    188         wcscmp(fd.cFileName, L"..") != 0)
    189     {
    190       wcscpy(path + len, fd.cFileName);
    191       if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
    192       {
    193         wcscat(path, WSTRING_PATH_SEPARATOR);
    194         res = RemoveDirWithSubItems(path);
    195       }
    196       else
    197       {
    198         SetFileAttributesW(path, 0);
    199         if (DeleteFileW(path) == 0)
    200           res = GetLastError();
    201       }
    202       if (res != 0)
    203         break;
    204     }
    205     if (!FindNextFileW(handle, &fd))
    206     {
    207       res = GetLastError();
    208       if (res == ERROR_NO_MORE_FILES)
    209         res = 0;
    210       break;
    211     }
    212   }
    213   path[len] = L'\0';
    214   FindClose(handle);
    215   if (res == 0)
    216   {
    217     if (!RemoveDirectoryW(path))
    218       res = GetLastError();
    219   }
    220   return res;
    221 }
    222 
    223 #ifdef _CONSOLE
    224 int MY_CDECL main()
    225 #else
    226 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    227   #ifdef UNDER_CE
    228   LPWSTR
    229   #else
    230   LPSTR
    231   #endif
    232   lpCmdLine, int nCmdShow)
    233 #endif
    234 {
    235   CFileInStream archiveStream;
    236   CLookToRead lookStream;
    237   CSzArEx db;
    238   SRes res = SZ_OK;
    239   ISzAlloc allocImp;
    240   ISzAlloc allocTempImp;
    241   WCHAR sfxPath[MAX_PATH + 2];
    242   WCHAR path[MAX_PATH * 3 + 2];
    243   #ifndef UNDER_CE
    244   WCHAR workCurDir[MAX_PATH + 32];
    245   #endif
    246   size_t pathLen;
    247   DWORD winRes;
    248   const wchar_t *cmdLineParams;
    249   const char *errorMessage = NULL;
    250   Bool useShellExecute = True;
    251 
    252   #ifdef _CONSOLE
    253   SetConsoleCtrlHandler(HandlerRoutine, TRUE);
    254   #else
    255   hInstance = hInstance;
    256   hPrevInstance = hPrevInstance;
    257   lpCmdLine = lpCmdLine;
    258   nCmdShow = nCmdShow;
    259   #endif
    260 
    261   CrcGenerateTable();
    262 
    263   allocImp.Alloc = SzAlloc;
    264   allocImp.Free = SzFree;
    265 
    266   allocTempImp.Alloc = SzAllocTemp;
    267   allocTempImp.Free = SzFreeTemp;
    268 
    269   FileInStream_CreateVTable(&archiveStream);
    270   LookToRead_CreateVTable(&lookStream, False);
    271 
    272   winRes = GetModuleFileNameW(NULL, sfxPath, MAX_PATH);
    273   if (winRes == 0 || winRes > MAX_PATH)
    274     return 1;
    275   {
    276     cmdLineParams = GetCommandLineW();
    277     #ifndef UNDER_CE
    278     {
    279       Bool quoteMode = False;
    280       for (;; cmdLineParams++)
    281       {
    282         wchar_t c = *cmdLineParams;
    283         if (c == L'\"')
    284           quoteMode = !quoteMode;
    285         else if (c == 0 || (c == L' ' && !quoteMode))
    286           break;
    287       }
    288     }
    289     #endif
    290   }
    291 
    292   {
    293     unsigned i;
    294     DWORD d;
    295     winRes = GetTempPathW(MAX_PATH, path);
    296     if (winRes == 0 || winRes > MAX_PATH)
    297       return 1;
    298     pathLen = wcslen(path);
    299     d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId();
    300 
    301     for (i = 0;; i++, d += GetTickCount())
    302     {
    303       if (i >= 100)
    304       {
    305         res = SZ_ERROR_FAIL;
    306         break;
    307       }
    308       wcscpy(path + pathLen, L"7z");
    309 
    310       {
    311         wchar_t *s = path + wcslen(path);
    312         UInt32 value = d;
    313         unsigned k;
    314         for (k = 0; k < 8; k++)
    315         {
    316           unsigned t = value & 0xF;
    317           value >>= 4;
    318           s[7 - k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10)));
    319         }
    320         s[k] = '\0';
    321       }
    322 
    323       if (DoesFileOrDirExist(path))
    324         continue;
    325       if (CreateDirectoryW(path, NULL))
    326       {
    327         wcscat(path, WSTRING_PATH_SEPARATOR);
    328         pathLen = wcslen(path);
    329         break;
    330       }
    331       if (GetLastError() != ERROR_ALREADY_EXISTS)
    332       {
    333         res = SZ_ERROR_FAIL;
    334         break;
    335       }
    336     }
    337 
    338     #ifndef UNDER_CE
    339     wcscpy(workCurDir, path);
    340     #endif
    341     if (res != SZ_OK)
    342       errorMessage = "Can't create temp folder";
    343   }
    344 
    345   if (res != SZ_OK)
    346   {
    347     if (!errorMessage)
    348       errorMessage = "Error";
    349     PrintErrorMessage(errorMessage);
    350     return 1;
    351   }
    352 
    353   if (InFile_OpenW(&archiveStream.file, sfxPath) != 0)
    354   {
    355     errorMessage = "can not open input file";
    356     res = SZ_ERROR_FAIL;
    357   }
    358   else
    359   {
    360     UInt64 pos = 0;
    361     if (!FindSignature(&archiveStream.file, &pos))
    362       res = SZ_ERROR_FAIL;
    363     else if (File_Seek(&archiveStream.file, (Int64 *)&pos, SZ_SEEK_SET) != 0)
    364       res = SZ_ERROR_FAIL;
    365     if (res != 0)
    366       errorMessage = "Can't find 7z archive";
    367   }
    368 
    369   if (res == SZ_OK)
    370   {
    371     lookStream.realStream = &archiveStream.s;
    372     LookToRead_Init(&lookStream);
    373   }
    374 
    375   SzArEx_Init(&db);
    376   if (res == SZ_OK)
    377   {
    378     res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp);
    379   }
    380 
    381   if (res == SZ_OK)
    382   {
    383     UInt32 executeFileIndex = (UInt32)(Int32)-1;
    384     UInt32 minPrice = 1 << 30;
    385     UInt32 i;
    386     UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
    387     Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */
    388     size_t outBufferSize = 0;  /* it can have any value before first call (if outBuffer = 0) */
    389 
    390     for (i = 0; i < db.NumFiles; i++)
    391     {
    392       size_t offset = 0;
    393       size_t outSizeProcessed = 0;
    394       size_t len;
    395       WCHAR *temp;
    396       len = SzArEx_GetFileNameUtf16(&db, i, NULL);
    397 
    398       if (len >= MAX_PATH)
    399       {
    400         res = SZ_ERROR_FAIL;
    401         break;
    402       }
    403 
    404       temp = path + pathLen;
    405 
    406       SzArEx_GetFileNameUtf16(&db, i, temp);
    407       {
    408         res = SzArEx_Extract(&db, &lookStream.s, i,
    409           &blockIndex, &outBuffer, &outBufferSize,
    410           &offset, &outSizeProcessed,
    411           &allocImp, &allocTempImp);
    412         if (res != SZ_OK)
    413           break;
    414       }
    415       {
    416         CSzFile outFile;
    417         size_t processedSize;
    418         size_t j;
    419         size_t nameStartPos = 0;
    420         for (j = 0; temp[j] != 0; j++)
    421         {
    422           if (temp[j] == '/')
    423           {
    424             temp[j] = 0;
    425             MyCreateDir(path);
    426             temp[j] = CHAR_PATH_SEPARATOR;
    427             nameStartPos = j + 1;
    428           }
    429         }
    430 
    431         if (SzArEx_IsDir(&db, i))
    432         {
    433           MyCreateDir(path);
    434           continue;
    435         }
    436         else
    437         {
    438           unsigned extLen;
    439           const WCHAR *name = temp + nameStartPos;
    440           unsigned len = (unsigned)wcslen(name);
    441           unsigned nameLen = FindExt(temp + nameStartPos, &extLen);
    442           unsigned extPrice = FindItem(kExts, sizeof(kExts) / sizeof(kExts[0]), name + len - extLen, extLen);
    443           unsigned namePrice = FindItem(kNames, sizeof(kNames) / sizeof(kNames[0]), name, nameLen);
    444 
    445           unsigned price = namePrice + extPrice * 64 + (nameStartPos == 0 ? 0 : (1 << 12));
    446           if (minPrice > price)
    447           {
    448             minPrice = price;
    449             executeFileIndex = i;
    450             useShellExecute = (extPrice != k_EXE_ExtIndex);
    451           }
    452 
    453           if (DoesFileOrDirExist(path))
    454           {
    455             errorMessage = "Duplicate file";
    456             res = SZ_ERROR_FAIL;
    457             break;
    458           }
    459           if (OutFile_OpenW(&outFile, path))
    460           {
    461             errorMessage = "Can't open output file";
    462             res = SZ_ERROR_FAIL;
    463             break;
    464           }
    465         }
    466 
    467         processedSize = outSizeProcessed;
    468         if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed)
    469         {
    470           errorMessage = "Can't write output file";
    471           res = SZ_ERROR_FAIL;
    472         }
    473 
    474         #ifdef USE_WINDOWS_FILE
    475         if (SzBitWithVals_Check(&db.MTime, i))
    476         {
    477           const CNtfsFileTime *t = db.MTime.Vals + i;
    478           FILETIME mTime;
    479           mTime.dwLowDateTime = t->Low;
    480           mTime.dwHighDateTime = t->High;
    481           SetFileTime(outFile.handle, NULL, NULL, &mTime);
    482         }
    483         #endif
    484 
    485         {
    486           SRes res2 = File_Close(&outFile);
    487           if (res != SZ_OK)
    488             break;
    489           if (res2 != SZ_OK)
    490           {
    491             res = res2;
    492             break;
    493           }
    494         }
    495         #ifdef USE_WINDOWS_FILE
    496         if (SzBitWithVals_Check(&db.Attribs, i))
    497           SetFileAttributesW(path, db.Attribs.Vals[i]);
    498         #endif
    499       }
    500     }
    501 
    502     if (res == SZ_OK)
    503     {
    504       if (executeFileIndex == (UInt32)(Int32)-1)
    505       {
    506         errorMessage = "There is no file to execute";
    507         res = SZ_ERROR_FAIL;
    508       }
    509       else
    510       {
    511         WCHAR *temp = path + pathLen;
    512         UInt32 j;
    513         SzArEx_GetFileNameUtf16(&db, executeFileIndex, temp);
    514         for (j = 0; temp[j] != 0; j++)
    515           if (temp[j] == '/')
    516             temp[j] = CHAR_PATH_SEPARATOR;
    517       }
    518     }
    519     IAlloc_Free(&allocImp, outBuffer);
    520   }
    521   SzArEx_Free(&db, &allocImp);
    522 
    523   File_Close(&archiveStream.file);
    524 
    525   if (res == SZ_OK)
    526   {
    527     HANDLE hProcess = 0;
    528 
    529     #ifndef UNDER_CE
    530     WCHAR oldCurDir[MAX_PATH + 2];
    531     oldCurDir[0] = 0;
    532     {
    533       DWORD needLen = GetCurrentDirectory(MAX_PATH + 1, oldCurDir);
    534       if (needLen == 0 || needLen > MAX_PATH)
    535         oldCurDir[0] = 0;
    536       SetCurrentDirectory(workCurDir);
    537     }
    538     #endif
    539 
    540     if (useShellExecute)
    541     {
    542       SHELLEXECUTEINFO ei;
    543       UINT32 executeRes;
    544       BOOL success;
    545 
    546       memset(&ei, 0, sizeof(ei));
    547       ei.cbSize = sizeof(ei);
    548       ei.lpFile = path;
    549       ei.fMask = SEE_MASK_NOCLOSEPROCESS
    550           #ifndef UNDER_CE
    551           | SEE_MASK_FLAG_DDEWAIT
    552           #endif
    553           /* | SEE_MASK_NO_CONSOLE */
    554           ;
    555       if (wcslen(cmdLineParams) != 0)
    556         ei.lpParameters = cmdLineParams;
    557       ei.nShow = SW_SHOWNORMAL; /* SW_HIDE; */
    558       success = ShellExecuteEx(&ei);
    559       executeRes = (UINT32)(UINT_PTR)ei.hInstApp;
    560       if (!success || (executeRes <= 32 && executeRes != 0))  /* executeRes = 0 in Windows CE */
    561         res = SZ_ERROR_FAIL;
    562       else
    563         hProcess = ei.hProcess;
    564     }
    565     else
    566     {
    567       STARTUPINFOW si;
    568       PROCESS_INFORMATION pi;
    569       WCHAR cmdLine[MAX_PATH * 3];
    570 
    571       wcscpy(cmdLine, path);
    572       wcscat(cmdLine, cmdLineParams);
    573       memset(&si, 0, sizeof(si));
    574       si.cb = sizeof(si);
    575       if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0)
    576         res = SZ_ERROR_FAIL;
    577       else
    578       {
    579         CloseHandle(pi.hThread);
    580         hProcess = pi.hProcess;
    581       }
    582     }
    583 
    584     if (hProcess != 0)
    585     {
    586       WaitForSingleObject(hProcess, INFINITE);
    587       CloseHandle(hProcess);
    588     }
    589 
    590     #ifndef UNDER_CE
    591     SetCurrentDirectory(oldCurDir);
    592     #endif
    593   }
    594 
    595   path[pathLen] = L'\0';
    596   RemoveDirWithSubItems(path);
    597 
    598   if (res == SZ_OK)
    599     return 0;
    600 
    601   {
    602     if (res == SZ_ERROR_UNSUPPORTED)
    603       errorMessage = "Decoder doesn't support this archive";
    604     else if (res == SZ_ERROR_MEM)
    605       errorMessage = "Can't allocate required memory";
    606     else if (res == SZ_ERROR_CRC)
    607       errorMessage = "CRC error";
    608     else
    609     {
    610       if (!errorMessage)
    611         errorMessage = "ERROR";
    612     }
    613     if (errorMessage)
    614       PrintErrorMessage(errorMessage);
    615   }
    616   return 1;
    617 }
    618