Compression.SevenZipDecoder.pas 12 KB

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