2
0

Compression.SevenZipDecoder.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. unit Compression.SevenZipDecoder;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Interface to the 7-Zip Decoder OBJ in Compression.SevenZipDecoder\7ZipDecode,
  8. used by Setup.
  9. }
  10. interface
  11. uses
  12. SysUtils;
  13. type
  14. TOnExtractionProgress = function(const ArchiveName, FileName: string; const Progress, ProgressMax: Int64): Boolean of object;
  15. ESevenZipError = class(Exception);
  16. procedure SevenZipError(const ExceptMessage: String; const LogMessage: String = '');
  17. procedure Extract7ZipArchiveRedir(const DisableFsRedir: Boolean;
  18. const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean;
  19. const OnExtractionProgress: TOnExtractionProgress);
  20. implementation
  21. uses
  22. Windows, Forms,
  23. PathFunc, UnsignedFunc,
  24. Shared.SetupMessageIDs, Shared.CommonFunc, SetupLdrAndSetup.Messages,
  25. Setup.RedirFunc, Setup.LoggingFunc, Setup.MainFunc, Setup.InstFunc;
  26. type
  27. TSevenZipDecodeState = record
  28. DisableFsRedir: Boolean;
  29. ExpandedArchiveFileName, ExpandedDestDir: String;
  30. LogBuffer: AnsiString;
  31. ExtractedArchiveName: String;
  32. OnExtractionProgress: TOnExtractionProgress;
  33. LastReportedProgress, LastReportedProgressMax: UInt64;
  34. Aborted: Boolean;
  35. end;
  36. var
  37. State: TSevenZipDecodeState;
  38. { Compiled by Visual Studio 2022 using compile.bat }
  39. {$IFNDEF WIN64}
  40. { To enable source debugging recompile using compile-bcc32c.bat and turn off the VISUALSTUDIO define below
  41. Note that in a speed test the code produced by bcc32c was about 33% slower }
  42. {$L Src\Compression.SevenZipDecoder\7zDecode\IS7zDec-x86.obj}
  43. {$ELSE}
  44. {$L Src\Compression.SevenZipDecoder\7zDecode\IS7zDec-x64.obj}
  45. {$ENDIF}
  46. {$DEFINE VISUALSTUDIO}
  47. function IS_7zDec(const fileName: PChar; const fullPaths: Bool): Integer; cdecl; external name {$IFNDEF WIN64} '_IS_7zDec' {$ELSE} 'IS_7zDec' {$ENDIF};
  48. function {$IFNDEF WIN64} __CreateDirectoryW {$ELSE} _CreateDirectoryW {$ENDIF}(
  49. lpPathName: LPCWSTR;
  50. lpSecurityAttributes: PSecurityAttributes): BOOL; cdecl;
  51. begin
  52. var ExpandedDir: String;
  53. if ValidateAndCombinePath(State.ExpandedDestDir, lpPathName, ExpandedDir) then
  54. Result := CreateDirectoryRedir(State.DisableFsRedir, ExpandedDir, lpSecurityAttributes)
  55. else begin
  56. Result := False;
  57. SetLastError(ERROR_ACCESS_DENIED);
  58. end;
  59. end;
  60. { Never actually called but still required by the linker }
  61. function {$IFNDEF WIN64} __CreateFileA {$ELSE} _CreateFileA {$ENDIF}(
  62. lpFileName: LPCSTR; dwDesiredAccess, dwShareMode: DWORD;
  63. lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD;
  64. hTemplateFile: THandle): THandle; cdecl;
  65. begin
  66. { Return an error if we do ever get called which is unwanted because it should
  67. use CreateFileW and not CreateFileA }
  68. Result := INVALID_HANDLE_VALUE;
  69. SetLastError(ERROR_INVALID_FUNCTION);
  70. end;
  71. function {$IFNDEF WIN64} __CreateFileW {$ELSE} _CreateFileW {$ENDIF}(
  72. lpFileName: LPCWSTR; dwDesiredAccess, dwShareMode: DWORD;
  73. lpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD;
  74. hTemplateFile: THandle): THandle; cdecl;
  75. begin
  76. { Filenames read from archives aren't validated at all by the SDK's 7zMain.c,
  77. so we have to handle that ourself. Most importantly, we need to make sure a
  78. malicious archive cannot create files outside of the destination directory. }
  79. var ExpandedFileName: String;
  80. if ((dwDesiredAccess = GENERIC_READ) and
  81. PathExpand(lpFileName, ExpandedFileName) and
  82. (PathCompare(ExpandedFileName, State.ExpandedArchiveFileName) = 0)) or
  83. ((dwDesiredAccess = GENERIC_WRITE) and
  84. ValidateAndCombinePath(State.ExpandedDestDir, lpFileName, ExpandedFileName)) then
  85. Result := CreateFileRedir(State.DisableFsRedir, ExpandedFileName,
  86. dwDesiredAccess, dwShareMode, lpSecurityAttributes,
  87. dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile)
  88. else begin
  89. Result := INVALID_HANDLE_VALUE;
  90. SetLastError(ERROR_ACCESS_DENIED);
  91. end;
  92. end;
  93. {$IFDEF VISUALSTUDIO}
  94. function {$IFNDEF WIN64} __FileTimeToLocalFileTime {$ELSE} _FileTimeToLocalFileTime {$ENDIF}(
  95. lpFileTime: PFileTime; var lpLocalFileTime: TFileTime): BOOL; cdecl;
  96. begin
  97. Result := FileTimeToLocalFileTime(lpFileTime, lpLocalFileTime);
  98. end;
  99. { Never actually called but still required by the linker }
  100. function {$IFNDEF WIN64} __GetFileSize {$ELSE} _GetFileSize {$ENDIF}(
  101. hFile: THandle; lpFileSizeHigh: Pointer): DWORD; cdecl;
  102. begin
  103. Result := GetFileSize(hFile, lpFileSizeHigh);
  104. end;
  105. function {$IFNDEF WIN64} __ReadFile {$ELSE} _ReadFile {$ENDIF}(
  106. hFile: THandle; var Buffer; nNumberOfBytesToRead: DWORD;
  107. var lpNumberOfBytesRead: DWORD; lpOverlapped: POverlapped): BOOL; cdecl;
  108. begin
  109. Result := ReadFile(hFile, Buffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped);
  110. end;
  111. function {$IFNDEF WIN64} __GetFileAttributesW {$ELSE} _GetFileAttributesW {$ENDIF}(
  112. lpFileName: LPCWSTR): DWORD; cdecl;
  113. begin
  114. { See above }
  115. var ExpandedFileName: String;
  116. if ValidateAndCombinePath(State.ExpandedDestDir, lpFileName, ExpandedFileName) then
  117. Result := GetFileAttributesRedir(State.DisableFsRedir, ExpandedFileName)
  118. else begin
  119. Result := INVALID_FILE_ATTRIBUTES;
  120. SetLastError(ERROR_ACCESS_DENIED);
  121. end;
  122. end;
  123. function {$IFNDEF WIN64} __SetFileAttributesW {$ELSE} _SetFileAttributesW {$ENDIF}(
  124. lpFileName: LPCWSTR; dwFileAttributes: DWORD): BOOL; cdecl;
  125. begin
  126. { See above }
  127. var ExpandedFileName: String;
  128. if ValidateAndCombinePath(State.ExpandedDestDir, lpFileName, ExpandedFileName) then
  129. Result := SetFileAttributesRedir(State.DisableFsRedir, ExpandedFileName, dwFileAttributes)
  130. else begin
  131. Result := False;
  132. SetLastError(ERROR_ACCESS_DENIED);
  133. end;
  134. end;
  135. function {$IFNDEF WIN64} __SetFilePointer {$ELSE} _SetFilePointer {$ENDIF}(
  136. hFile: THandle; lDistanceToMove: Longint;
  137. lpDistanceToMoveHigh: Pointer; dwMoveMethod: DWORD): DWORD; cdecl;
  138. begin
  139. Result := SetFilePointer(hFile, lDistanceToMove, lpDistanceToMoveHigh, dwMoveMethod);
  140. end;
  141. function {$IFNDEF WIN64} __SetFileTime {$ELSE} _SetFileTime {$ENDIF}(
  142. hFile: THandle;
  143. lpCreationTime, lpLastAccessTime, lpLastWriteTime: PFileTime): BOOL; cdecl;
  144. begin
  145. Result := SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);
  146. end;
  147. function {$IFNDEF WIN64} __WriteFile {$ELSE} _WriteFile {$ENDIF}(
  148. hFile: THandle; const Buffer; nNumberOfBytesToWrite: DWORD;
  149. var lpNumberOfBytesWritten: DWORD; lpOverlapped: POverlapped): BOOL; cdecl;
  150. begin
  151. Result := WriteFile(hFile, Buffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
  152. end;
  153. function {$IFNDEF WIN64} __CloseHandle {$ELSE} _CloseHandle {$ENDIF}(
  154. hObject: THandle): BOOL; cdecl;
  155. begin
  156. Result := CloseHandle(hObject);
  157. end;
  158. function {$IFNDEF WIN64} __GetLastError {$ELSE} _GetLastError {$ENDIF}: DWORD; cdecl;
  159. begin
  160. Result := GetLastError;
  161. end;
  162. function {$IFNDEF WIN64} __LocalFree {$ELSE} _LocalFree {$ENDIF}(
  163. hMem: HLOCAL): HLOCAL; cdecl;
  164. begin
  165. Result := LocalFree(hMem);
  166. end;
  167. function {$IFNDEF WIN64} __FormatMessageA {$ELSE} _FormatMessageA {$ENDIF}(
  168. dwFlags: DWORD; lpSource: Pointer; dwMessageId: DWORD; dwLanguageId: DWORD;
  169. lpBuffer: LPSTR; nSize: DWORD; Arguments: Pointer): DWORD; cdecl;
  170. begin
  171. Result := FormatMessageA(dwFlags, lpSource, dwMessageId, dwLanguageId, lpBuffer, nSize, Arguments);
  172. end;
  173. function {$IFNDEF WIN64} __WideCharToMultiByte {$ELSE} _WideCharToMultiByte {$ENDIF}(
  174. CodePage: UINT; dwFlags: DWORD; lpWideCharStr: LPWSTR; cchWideChar: Integer; lpMultiByteStr: LPSTR;
  175. cchMultiByte: Integer; lpDefaultChar: LPCSTR; lpUsedDefaultChar: PBOOL): Integer; cdecl;
  176. begin
  177. Result := WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cchMultiByte, lpDefaultChar, lpUsedDefaultChar);
  178. end;
  179. {$IFNDEF WIN64}
  180. //https://github.com/rust-lang/compiler-builtins/issues/403
  181. procedure __allshl; register; external 'ntdll.dll' name '_allshl';
  182. procedure __aullshr; register; external 'ntdll.dll' name '_aullshr';
  183. {$ENDIF}
  184. {$ELSE}
  185. procedure __aullrem; stdcall; external 'ntdll.dll' name '_aullrem';
  186. procedure __aulldiv; stdcall; external 'ntdll.dll' name '_aulldiv';
  187. {$ENDIF}
  188. function {$IFNDEF WIN64} _memcpy {$ELSE} memcpy {$ENDIF}(
  189. dest, src: Pointer; n: NativeUInt): Pointer; cdecl;
  190. begin
  191. UMove(src^, dest^, n);
  192. Result := dest;
  193. end;
  194. function {$IFNDEF WIN64} _memset {$ELSE} memset {$ENDIF}(
  195. dest: Pointer; c: Integer; n: NativeUInt): Pointer; cdecl;
  196. begin
  197. UFillChar(dest^, n, c);
  198. Result := dest;
  199. end;
  200. function {$IFNDEF WIN64} _malloc {$ELSE} malloc {$ENDIF}(
  201. size: NativeUInt): Pointer; cdecl;
  202. begin
  203. if size > NativeUInt(High(NativeInt)) then
  204. Result := nil
  205. else begin
  206. try
  207. GetMem(Result, NativeInt(size));
  208. except
  209. on EOutOfMemory do
  210. Result := nil;
  211. end;
  212. end;
  213. end;
  214. procedure {$IFNDEF WIN64} _free {$ELSE} free {$ENDIF}(address: Pointer); cdecl;
  215. begin
  216. FreeMem(address);
  217. end;
  218. function {$IFNDEF WIN64} _wcscmp {$ELSE} wcscmp {$ENDIF}(
  219. string1, string2: PChar): Integer; cdecl;
  220. begin
  221. Result := StrComp(string1, string2);
  222. end;
  223. procedure Log(const S: AnsiString);
  224. begin
  225. if S <> '' then
  226. Setup.LoggingFunc.Log(UTF8ToString(S));
  227. end;
  228. function {$IFNDEF WIN64} __fputs {$ELSE} _fputs {$ENDIF}(
  229. str: PAnsiChar; unused: Pointer): Integer; cdecl;
  230. function FindNewLine(const S: AnsiString): Integer;
  231. begin
  232. { 7zMain.c always sends #10 as newline but its call to FormatMessage can cause #13#10 anyway }
  233. var N := Length(S);
  234. for var I := 1 to N do
  235. if CharInSet(S[I], [#13, #10]) then
  236. Exit(I);
  237. Result := 0;
  238. end;
  239. begin
  240. try
  241. State.LogBuffer := State.LogBuffer + str;
  242. var P := FindNewLine(State.LogBuffer);
  243. while P <> 0 do begin
  244. Log(Copy(State.LogBuffer, 1, P-1));
  245. if (State.LogBuffer[P] = #13) and (P < Length(State.LogBuffer)) and (State.LogBuffer[P+1] = #10) then
  246. Inc(P);
  247. Delete(State.LogBuffer, 1, P);
  248. P := FindNewLine(State.LogBuffer);
  249. end;
  250. Result := 0;
  251. except
  252. Result := -1; { EOF }
  253. end;
  254. end;
  255. procedure {$IFNDEF WIN64} _ReportProgress {$ELSE} ReportProgress {$ENDIF}(
  256. const FileName: PChar; const Progress, ProgressMax: UInt64; var Abort: Bool); cdecl;
  257. begin
  258. try
  259. if Assigned(State.OnExtractionProgress) then begin
  260. const MaxInt64 = High(Int64);
  261. var ReportProgress, ReportProgressMax: Int64;
  262. if Progress > MaxInt64 then
  263. ReportProgress := MaxInt64
  264. else
  265. ReportProgress := Int64(Progress);
  266. if ProgressMax > MaxInt64 then
  267. ReportProgressMax := MaxInt64
  268. else
  269. ReportProgressMax := Int64(ProgressMax);
  270. if not State.OnExtractionProgress(State.ExtractedArchiveName, FileName, ReportProgress, ReportProgressMax) then
  271. Abort := True;
  272. end;
  273. if not Abort and DownloadTemporaryFileOrExtractArchiveProcessMessages then
  274. Application.ProcessMessages;
  275. except
  276. Abort := True;
  277. end;
  278. if Abort then
  279. State.Aborted := True;
  280. end;
  281. procedure SevenZipError(const ExceptMessage, LogMessage: String);
  282. { LogMessage may be non-localized or empty but ExceptMessage may be neither.
  283. ExceptMessage should not already contain msgErrorExtractionFailed.
  284. Should not be called from a secondary thread if LogMessage is not empty. }
  285. begin
  286. if LogMessage <> '' then
  287. LogFmt('ERROR: %s', [LogMessage]); { Just like 7zMain.c }
  288. raise ESevenZipError.Create(ExceptMessage);
  289. end;
  290. procedure Extract7ZipArchiveRedir(const DisableFsRedir: Boolean;
  291. const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean;
  292. const OnExtractionProgress: TOnExtractionProgress);
  293. procedure BadResultError(const Res: Integer);
  294. const
  295. SZ_ERROR_DATA = 1;
  296. SZ_ERROR_MEM = 2;
  297. SZ_ERROR_CRC = 3;
  298. SZ_ERROR_UNSUPPORTED = 4;
  299. SZ_ERROR_ARCHIVE = 16;
  300. SZ_ERROR_NO_ARCHIVE = 17;
  301. begin
  302. { Logging already done by 7zMain.c }
  303. case Res of
  304. SZ_ERROR_UNSUPPORTED, SZ_ERROR_NO_ARCHIVE:
  305. SevenZipError(SetupMessages[msgArchiveUnsupportedFormat]);
  306. SZ_ERROR_DATA, SZ_ERROR_CRC, SZ_ERROR_ARCHIVE:
  307. SevenZipError(SetupMessages[msgArchiveIsCorrupted]);
  308. SZ_ERROR_MEM:
  309. SevenZipError(Win32ErrorString(DWORD(E_OUTOFMEMORY)));
  310. else
  311. SevenZipError(Res.ToString);
  312. end;
  313. end;
  314. begin
  315. LogArchiveExtractionModeOnce;
  316. if ArchiveFileName = '' then
  317. InternalError('Extract7ZipArchive: Invalid ArchiveFileName value');
  318. if DestDir = '' then
  319. InternalError('Extract7ZipArchive: Invalid DestDir value');
  320. if Password <> '' then
  321. InternalError('Extract7ZipArchive: Invalid Password value');
  322. LogFmt('Extracting 7-Zip archive %s to %s. Full paths? %s', [ArchiveFileName,
  323. RemoveBackslashUnlessRoot(DestDir), SYesNo[FullPaths]]);
  324. if not ForceDirectories(DisableFsRedir, DestDir) then
  325. SevenZipError(FmtSetupMessage1(msgErrorCreatingDir, DestDir), 'Failed to create destination directory');
  326. State.DisableFsRedir := DisableFsRedir;
  327. State.ExpandedArchiveFileName := PathExpand(ArchiveFileName);
  328. State.ExpandedDestDir := AddBackslash(PathExpand(DestDir));
  329. State.LogBuffer := '';
  330. State.ExtractedArchiveName := PathExtractName(ArchiveFileName);
  331. State.OnExtractionProgress := OnExtractionProgress;
  332. State.LastReportedProgress := 0;
  333. State.LastReportedProgressMax := 0;
  334. State.Aborted := False;
  335. var Res := IS_7zDec(PChar(ArchiveFileName), FullPaths);
  336. if State.LogBuffer <> '' then
  337. Log(State.LogBuffer);
  338. if State.Aborted then
  339. Abort
  340. else if Res <> 0 then
  341. BadResultError(Res);
  342. end;
  343. end.