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