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