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