2
0

Compression.SevenZipDecoder.pas 14 KB

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