Compression.SevenZipDecoder.pas 12 KB

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