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 
FindExt(const wchar_t * s,unsigned * extLen)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 
FindItem(const char * const * items,unsigned num,const wchar_t * s,unsigned len)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
HandlerRoutine(DWORD ctrlType)90 static BOOL WINAPI HandlerRoutine(DWORD ctrlType)
91 {
92   UNUSED_VAR(ctrlType);
93   return TRUE;
94 }
95 #endif
96 
PrintErrorMessage(const char * message)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 
MyCreateDir(const WCHAR * name)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 
FindSignature(CSzFile * stream,UInt64 * resPos)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 
DoesFileOrDirExist(const WCHAR * path)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 
RemoveDirWithSubItems(WCHAR * path)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
main()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