1/* SfxSetup.c - 7z SFX Setup
22016-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
27static 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
41static const char * const kNames[] =
42{
43    "setup"
44  , "install"
45  , "run"
46  , "start"
47};
48
49static 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
67static 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
90static BOOL WINAPI HandlerRoutine(DWORD ctrlType)
91{
92  UNUSED_VAR(ctrlType);
93  return TRUE;
94}
95#endif
96
97static 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
115static 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
128static 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
164static 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
175static 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
229int MY_CDECL main()
230#else
231int 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