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