Compression.SevenZipDLLDecoder.pas 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  1. unit Compression.SevenZipDLLDecoder;
  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 DLLs, used by Setup
  8. Based on the 7-Zip source code and the 7-Zip Delphi API by Henri Gourvest
  9. https://github.com/geoffsmith82/d7zip MPL 1.1 licensed
  10. }
  11. interface
  12. uses
  13. Windows, Shared.FileClass, Shared.VerInfoFunc, Compression.SevenZipDecoder;
  14. function SevenZipDLLInit(const SevenZipLibrary: HMODULE;
  15. [ref] const VersionNumbers: TFileVersionNumbers): Boolean;
  16. procedure SevenZipDLLDeInit;
  17. procedure MapArchiveExtensions(const DestExt, SourceExt: String);
  18. procedure ExtractArchiveRedir(const DisableFsRedir: Boolean;
  19. const ArchiveFilename, DestDir, Password: String; const FullPaths: Boolean;
  20. const OnExtractionProgress: TOnExtractionProgress);
  21. { These functions work similar to Windows' FindFirstFile, FindNextFile, and
  22. FindClose with the exception that recursion is built-in and that the
  23. resulting FindFileData.cFilename contains not just a filename but also the
  24. subdir. Also, ArchiveFindFirstFileRedir throws an exception for most errors:
  25. INVALID_HANDLE_VALUE is only used if the archive is ok but no suitable file
  26. was found. }
  27. type
  28. TArchiveFindHandle = type NativeUInt;
  29. TOnExtractToHandleProgress = procedure(const Bytes, Param: Int64);
  30. function ArchiveFindFirstFileRedir(const DisableFsRedir: Boolean;
  31. const ArchiveFilename, DestDir, Password: String;
  32. const RecurseSubDirs, ExtractIntent: Boolean;
  33. out FindFileData: TWin32FindData): TArchiveFindHandle;
  34. function ArchiveFindNextFile(const FindFile: TArchiveFindHandle; out FindFileData: TWin32FindData): Boolean;
  35. function ArchiveFindClose(const FindFile: TArchiveFindHandle): Boolean;
  36. procedure ArchiveFindExtract(const FindFile: TArchiveFindHandle; const DestF: TFile;
  37. const OnExtractToHandleProgress: TOnExtractToHandleProgress; const OnExtractToHandleProgressParam: Int64);
  38. implementation
  39. uses
  40. Classes, SysUtils, Forms, Variants, ActiveX, ComObj, Generics.Collections, Generics.Defaults,
  41. Compression.SevenZipDLLDecoder.Interfaces, PathFunc,
  42. Shared.SetupMessageIDs, Shared.CommonFunc,
  43. SetupLdrAndSetup.Messages,
  44. Setup.RedirFunc, Setup.LoggingFunc, Setup.MainFunc, Setup.InstFunc;
  45. type
  46. TInStream = class(TInterfacedObject, IInStream)
  47. private
  48. FFile: TFile;
  49. protected
  50. function Read(data: Pointer; size: UInt32; processedSize: PUInt32): HRESULT; stdcall;
  51. function Seek(offset: Int64; seekOrigin: UInt32; newPosition: PUInt64): HRESULT; stdcall;
  52. public
  53. constructor Create(const AFile: TFile);
  54. destructor Destroy; override;
  55. end;
  56. TSequentialOutStream = class(TInterfacedObject, ISequentialOutStream)
  57. private
  58. FFile: TFile;
  59. protected
  60. function Write(data: Pointer; size: UInt32; processedSize: PUInt32): HRESULT; stdcall;
  61. public
  62. constructor Create(const AFileToBeDuplicated: TFile);
  63. destructor Destroy; override;
  64. end;
  65. TArchiveCallback = class(TInterfacedObject, ICryptoGetTextPassword)
  66. private
  67. FPassword: String;
  68. protected
  69. { ICryptoGetTextPassword - queried for by 7-Zip both on IArchiveOpenCallback
  70. and IArchiveExtractCallback instances - note: have not yet seen 7-Zip actually
  71. call it on an IArchiveOpenCallback instance }
  72. function CryptoGetTextPassword(out password: WideString): HRESULT; stdcall;
  73. public
  74. constructor Create(const Password: String);
  75. end;
  76. TArchiveOpenCallback = class(TArchiveCallback, IArchiveOpenCallback)
  77. protected
  78. { IArchiveOpenCallback }
  79. function SetTotal(files, bytes: PUInt64): HRESULT; stdcall;
  80. function SetCompleted(files, bytes: PUInt64): HRESULT; stdcall;
  81. end;
  82. TArchiveOpenCallbackWithStreamBackup = class(TArchiveOpenCallback)
  83. private
  84. FStreamBackup: IInStream;
  85. public
  86. constructor Create(const Password: String; const StreamToBackup: IInStream);
  87. end;
  88. TArchiveOpenFileCallback = class(TArchiveOpenCallback, IArchiveOpenVolumeCallback)
  89. private
  90. FDisableFsRedir: Boolean;
  91. FArchiveFilename: String;
  92. protected
  93. { IArchiveOpenVolumeCallback - queried for by 7-Zip on IArchiveOpenCallback }
  94. function GetProperty(propID: PROPID; var value: OleVariant): HRESULT; stdcall;
  95. function GetStream(const name: PChar; var inStream: IInStream): HRESULT; stdcall;
  96. public
  97. constructor Create(const DisableFsRedir: Boolean; const ArchiveFilename, Password: String);
  98. end;
  99. TArchiveExtractBaseCallback = class(TArchiveCallback, IArchiveExtractCallback)
  100. private
  101. type
  102. TResult = record
  103. SavedFatalException: TObject;
  104. Res: HRESULT;
  105. OpRes: TNOperationResult;
  106. end;
  107. TArrayOfUInt32 = array of UInt32;
  108. var
  109. FInArchive: IInArchive;
  110. FnumItems: UInt32;
  111. FLock: TObject;
  112. FProgress, FProgressMax: Int64;
  113. FAbort: Boolean;
  114. FResult: TResult;
  115. protected
  116. { IProgress }
  117. function SetTotal(total: UInt64): HRESULT; stdcall;
  118. function SetCompleted(completeValue: PUInt64): HRESULT; stdcall;
  119. { IArchiveExtractCallback }
  120. function GetStream(index: UInt32; out outStream: ISequentialOutStream;
  121. askExtractMode: Int32): HRESULT; virtual; stdcall; abstract;
  122. function PrepareOperation(askExtractMode: Int32): HRESULT; stdcall;
  123. function SetOperationResult(opRes: TNOperationResult): HRESULT; virtual; stdcall;
  124. { Other }
  125. function GetIndices: TArrayOfUInt32; virtual; abstract;
  126. procedure Extract;
  127. procedure HandleProgress; virtual; abstract;
  128. procedure HandleResult;
  129. public
  130. constructor Create(const InArchive: IInArchive; const numItems: UInt32;
  131. const Password: String);
  132. destructor Destroy; override;
  133. end;
  134. TArchiveExtractAllCallback = class(TArchiveExtractBaseCallback)
  135. private
  136. type
  137. TCurrent = record
  138. Path, ExpandedPath: String;
  139. HasAttrib: Boolean;
  140. Attrib: DWORD;
  141. CTime, MTime: TFileTime;
  142. outStream: ISequentialOutStream;
  143. procedure SetAttrib(const AAttrib: DWORD);
  144. end;
  145. var
  146. FDisableFsRedir: Boolean;
  147. FExpandedDestDir: String;
  148. FFullPaths: Boolean;
  149. FExtractedArchiveName: String;
  150. FOnExtractionProgress: TOnExtractionProgress;
  151. FCurrent: TCurrent; { Protected by base's FLock }
  152. FLogQueue: TStrings; { Same }
  153. protected
  154. { IArchiveExtractCallback }
  155. function GetStream(index: UInt32; out outStream: ISequentialOutStream;
  156. askExtractMode: Int32): HRESULT; override; stdcall;
  157. function SetOperationResult(opRes: TNOperationResult): HRESULT; override; stdcall;
  158. { Other }
  159. function GetIndices: TArchiveExtractBaseCallback.TArrayOfUInt32; override;
  160. procedure HandleProgress; override;
  161. public
  162. constructor Create(const InArchive: IInArchive; const numItems: UInt32;
  163. const DisableFsRedir: Boolean; const ArchiveFileName, DestDir, Password: String;
  164. const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
  165. destructor Destroy; override;
  166. end;
  167. TArchiveExtractToHandleCallback = class(TArchiveExtractBaseCallback)
  168. private
  169. FIndex: UInt32;
  170. FDestF: TFile;
  171. FOnExtractToHandleProgress: TOnExtractToHandleProgress;
  172. FOnExtractToHandleProgressParam: Int64;
  173. FPreviousProgress: Int64;
  174. protected
  175. { IArchiveExtractCallback }
  176. function GetStream(index: UInt32; out outStream: ISequentialOutStream;
  177. askExtractMode: Int32): HRESULT; override; stdcall;
  178. { Other }
  179. function GetIndices: TArchiveExtractBaseCallback.TArrayOfUInt32; override;
  180. procedure HandleProgress; override;
  181. public
  182. constructor Create(const InArchive: IInArchive; const numItems: UInt32;
  183. const Password: String; const Index: UInt32; const DestF: TFile;
  184. const OnExtractToHandleProgress: TOnExtractToHandleProgress;
  185. const OnExtractToHandleProgressParam: Int64);
  186. destructor Destroy; override;
  187. end;
  188. { Helper functions }
  189. procedure SevenZipWin32Error(const FunctionName: String; const ErrorCode: DWORD); overload;
  190. begin
  191. const ExceptMessage = FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  192. [FunctionName, IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]);
  193. const LogMessage = Format('Function %s returned error code %d', [FunctionName, ErrorCode]);
  194. SevenZipError(ExceptMessage, LogMessage);
  195. end;
  196. procedure SevenZipWin32Error(const FunctionName: String); overload;
  197. begin
  198. SevenZipWin32Error(FunctionName, GetLastError);
  199. end;
  200. function GetHandler(const Filename, NotFoundErrorMsg: String): TGUID; forward;
  201. const
  202. varFileTime = 64; { Delphi lacks proper VT_FILETIME support }
  203. type
  204. TVarTypeSet = set of varEmpty..varFileTime; { Incomplete but don't need others }
  205. function GetProperty(const InArchive: IInArchive; const index: UInt32;
  206. const propID: PROPID; const allowedTypes: TVarTypeSet; out value: OleVariant): Boolean; overload;
  207. { Raises an EOleSysError exception on error but otherwise always sets value,
  208. returning True if it's not empty. Set index to $FFFF to query an archive property
  209. instead of an item propery }
  210. begin
  211. var Res: HRESULT;
  212. if index = $FFFF then
  213. Res := InArchive.GetArchiveProperty(propID, value)
  214. else
  215. Res := InArchive.GetProperty(index, propID, value);
  216. if Res <> S_OK then
  217. OleError(Res);
  218. Result := not VarIsEmpty(Value);
  219. if Result and not (VarType(value) in allowedTypes) then
  220. OleError(E_FAIL);
  221. end;
  222. function GetProperty(const InArchive: IInArchive; index: UInt32; propID: PROPID;
  223. out value: String): Boolean; overload;
  224. begin
  225. var varValue: OleVariant;
  226. Result := GetProperty(InArchive, index, propID, [varOleStr], varValue);
  227. value := varValue;
  228. end;
  229. function GetProperty(const InArchive: IInArchive; index: UInt32; propID: PROPID;
  230. out value: Cardinal): Boolean; overload;
  231. begin
  232. var varValue: OleVariant;
  233. Result := GetProperty(InArchive, index, propID, [varUInt32], varValue);
  234. value := varValue;
  235. end;
  236. function GetProperty(const InArchive: IInArchive; index: UInt32; propID: PROPID;
  237. out value: Boolean): Boolean; overload;
  238. begin
  239. var varValue: OleVariant;
  240. Result := GetProperty(InArchive, index, propID, [varBoolean], varValue);
  241. value := varValue;
  242. end;
  243. function GetProperty(const InArchive: IInArchive; index: UInt32; propID: PROPID;
  244. out value: UInt64): Boolean; overload;
  245. begin
  246. var varValue: OleVariant;
  247. Result := GetProperty(InArchive, index, propID, [varUInt64], varValue);
  248. value := varValue;
  249. end;
  250. function GetProperty(const InArchive: IInArchive; index: UInt32; propID: PROPID;
  251. out value: TFileTime): Boolean; overload;
  252. begin
  253. var varValue: OleVariant;
  254. Result := GetProperty(InArchive, index, propID, [varFileTime], varValue);
  255. if Result then
  256. value := TFileTime(TVarData(varValue).VInt64)
  257. else
  258. value.Clear;
  259. end;
  260. procedure PosixHighDetect(var Attrib: DWORD);
  261. begin
  262. { "PosixHighDetect", just like FileDir.cpp and similar to 7zMain.c }
  263. if Attrib and $F0000000 <> 0 then
  264. Attrib := Attrib and $3FFF;
  265. end;
  266. { TInStream }
  267. constructor TInStream.Create(const AFile: TFile);
  268. begin
  269. inherited Create;
  270. FFile := AFile;
  271. end;
  272. destructor TInStream.Destroy;
  273. begin
  274. FFile.Free;
  275. inherited;
  276. end;
  277. function TInStream.Read(data: Pointer; size: UInt32;
  278. processedSize: PUInt32): HRESULT;
  279. begin
  280. try
  281. var BytesRead := FFile.Read(data^, size);
  282. if processedSize <> nil then
  283. processedSize^ := BytesRead;
  284. Result := S_OK;
  285. except
  286. on E: EAbort do
  287. Result := E_ABORT
  288. else
  289. Result := E_FAIL;
  290. end;
  291. end;
  292. function TInStream.Seek(offset: Int64; seekOrigin: UInt32;
  293. newPosition: PUInt64): HRESULT;
  294. begin
  295. try
  296. case seekOrigin of
  297. STREAM_SEEK_SET: FFile.Seek(offset);
  298. STREAM_SEEK_CUR: FFile.Seek(FFile.Position + offset);
  299. STREAM_SEEK_END: FFile.Seek(FFile.Size + offset);
  300. else
  301. Exit(E_INVALIDARG);
  302. end;
  303. if newPosition <> nil then
  304. newPosition^ := UInt64(FFile.Position);
  305. Result := S_OK;
  306. except
  307. on E: EAbort do
  308. Result := E_ABORT
  309. else
  310. Result := E_FAIL;
  311. end;
  312. end;
  313. { TSequentialOutStream }
  314. constructor TSequentialOutStream.Create(const AFileToBeDuplicated: TFile);
  315. begin
  316. inherited Create;
  317. FFile := TFile.CreateDuplicate(AFileToBeDuplicated);
  318. end;
  319. destructor TSequentialOutStream.Destroy;
  320. begin
  321. FFile.Free;
  322. inherited;
  323. end;
  324. function TSequentialOutStream.Write(data: Pointer; size: UInt32;
  325. processedSize: PUInt32): HRESULT;
  326. begin
  327. try
  328. FFile.WriteBuffer(data^, size);
  329. if processedSize <> nil then
  330. processedSize^ := size;
  331. Result := S_OK;
  332. except
  333. on E: EAbort do
  334. Result := E_ABORT
  335. else
  336. Result := E_FAIL;
  337. end;
  338. end;
  339. { TArchiveCallback }
  340. constructor TArchiveCallback.Create(const Password: String);
  341. begin
  342. inherited Create;
  343. FPassword := Password;
  344. end;
  345. function TArchiveCallback.CryptoGetTextPassword(
  346. out password: WideString): HRESULT;
  347. begin
  348. try
  349. password := FPassword;
  350. Result := S_OK;
  351. except
  352. on E: EAbort do
  353. Result := E_ABORT
  354. else
  355. Result := E_FAIL;
  356. end
  357. end;
  358. { TArchiveOpenCallback }
  359. function TArchiveOpenCallback.SetCompleted(files,
  360. bytes: PUInt64): HRESULT;
  361. begin
  362. Result := S_OK;
  363. end;
  364. function TArchiveOpenCallback.SetTotal(files,
  365. bytes: PUInt64): HRESULT;
  366. begin
  367. Result := S_OK;
  368. end;
  369. { TArchiveOpenCallbackWithStreamBackup }
  370. constructor TArchiveOpenCallbackWithStreamBackup.Create(const Password: String;
  371. const StreamToBackup: IInStream);
  372. begin
  373. inherited Create(Password);
  374. FStreamBackup := StreamToBackup;
  375. end;
  376. { TArchiveOpenFileCallback }
  377. constructor TArchiveOpenFileCallback.Create(const DisableFsRedir: Boolean; const ArchiveFilename,
  378. Password: String);
  379. begin
  380. inherited Create(Password);
  381. FDisableFsRedir := DisableFsRedir;
  382. FArchiveFilename := ArchiveFilename;
  383. end;
  384. function TArchiveOpenFileCallback.GetProperty(propID: PROPID; var value: OleVariant): HRESULT;
  385. begin
  386. { This is for multi-volume archives: when the archive is opened 7-Zip only receives a stream. It
  387. will then use this callback to find the name of the archive (like archive.7z.001) to figure out
  388. the name of other volumes (like archive.7z.002) }
  389. if propID = kpidName then
  390. value := FArchiveFilename
  391. else
  392. value := Unassigned; { Note sure if this is really needed }
  393. Result := S_OK;
  394. end;
  395. function TArchiveOpenFileCallback.GetStream(const name: PChar; var inStream: IInStream): HRESULT;
  396. begin
  397. { This is for multi-volume archives: after 7-Zip figures out the name of other volumes (like
  398. archive.7z.002) it will then use this callback to open it. The callback must either return
  399. S_FALSE or set instream to nil when it tries to open a volume which doesn't exists (like
  400. archive.7z.003 when there's two volumes only). }
  401. try
  402. if NewFileExistsRedir(FDisableFsRedir, name) then begin
  403. const F = TFileRedir.Create(FDisableFsRedir, name, fdOpenExisting, faRead, fsRead);
  404. instream := TInStream.Create(F);
  405. end else
  406. instream := nil;
  407. Result := S_OK;
  408. except
  409. on E: EAbort do
  410. Result := E_ABORT
  411. else
  412. Result := E_FAIL;
  413. end;
  414. end;
  415. { TArchiveExtractBaseCallback }
  416. constructor TArchiveExtractBaseCallback.Create(const InArchive: IInArchive;
  417. const numItems: UInt32; const Password: String);
  418. begin
  419. inherited Create(Password);
  420. FInArchive := InArchive;
  421. FnumItems := numItems;
  422. FLock := TObject.Create;
  423. FResult.OpRes := kOK;
  424. end;
  425. destructor TArchiveExtractBaseCallback.Destroy;
  426. begin
  427. FResult.SavedFatalException.Free;
  428. FLock.Free;
  429. inherited;
  430. end;
  431. function TArchiveExtractBaseCallback.SetTotal(total: UInt64): HRESULT;
  432. begin
  433. { From IArchive.h: 7-Zip can call functions for IProgress or ICompressProgressInfo functions
  434. from another threads simultaneously with calls for IArchiveExtractCallback interface }
  435. try
  436. System.TMonitor.Enter(FLock);
  437. try
  438. const MaxInt64 = High(Int64);
  439. if total > MaxInt64 then
  440. FProgressMax := MaxInt64
  441. else
  442. FProgressMax := Int64(total);
  443. finally
  444. System.TMonitor.Exit(FLock);
  445. end;
  446. Result := S_OK;
  447. except
  448. on E: EAbort do
  449. Result := E_ABORT
  450. else
  451. Result := E_FAIL;
  452. end;
  453. end;
  454. function TArchiveExtractBaseCallback.SetCompleted(completeValue: PUInt64): HRESULT;
  455. begin
  456. try
  457. if FAbort then
  458. SysUtils.Abort;
  459. System.TMonitor.Enter(FLock);
  460. try
  461. const MaxInt64 = High(Int64);
  462. if completeValue^ > MaxInt64 then
  463. FProgress := MaxInt64
  464. else
  465. FProgress := Int64(completeValue^);
  466. finally
  467. System.TMonitor.Exit(FLock);
  468. end;
  469. Result := S_OK;
  470. except
  471. on E: EAbort do
  472. Result := E_ABORT
  473. else
  474. Result := E_FAIL;
  475. end;
  476. end;
  477. function TArchiveExtractBaseCallback.PrepareOperation(askExtractMode: Int32): HRESULT;
  478. begin
  479. { From Client7z.cpp: PrepareOperation is called *after* GetStream has been called }
  480. Result := S_OK;
  481. end;
  482. function TArchiveExtractBaseCallback.SetOperationResult(
  483. opRes: TNOperationResult): HRESULT;
  484. begin
  485. try
  486. if opRes <> kOK then begin
  487. FResult.OpRes := opRes;
  488. Result := E_FAIL; { Make sure it doesn't continue with the next file }
  489. end else
  490. Result := S_OK;
  491. except
  492. on E: EAbort do
  493. Result := E_ABORT
  494. else
  495. Result := E_FAIL;
  496. end;
  497. end;
  498. function ExtractThreadFunc(Parameter: Pointer): Integer;
  499. begin
  500. const E = TArchiveExtractBaseCallback(Parameter);
  501. try
  502. const Indices = E.GetIndices;
  503. const NIndices = Cardinal(Length(Indices));
  504. if NIndices > 0 then begin
  505. { From IArchive.h: indices must be sorted. Also: 7-Zip's code crashes if
  506. sent an invalid index. So we check them fully. }
  507. for var I := 0 to NIndices-1 do
  508. if (Indices[I] >= E.FnumItems) or ((I > 0) and (Indices[I-1] >= Indices[I])) then
  509. InternalError('NIndices invalid');
  510. E.FResult.Res := E.FInArchive.Extract(@Indices[0], NIndices, 0, E)
  511. end else
  512. E.FResult.Res := E.FInArchive.Extract(nil, $FFFFFFFF, 0, E)
  513. except
  514. const Ex = AcquireExceptionObject;
  515. MemoryBarrier;
  516. E.FResult.SavedFatalException := Ex;
  517. end;
  518. { Be extra sure FSavedFatalException (and everything else) is made visible
  519. prior to thread termination. (Likely redundant, but you never know...) }
  520. MemoryBarrier;
  521. Result := 0;
  522. end;
  523. procedure TArchiveExtractBaseCallback.Extract;
  524. begin
  525. { We're calling 7-Zip's Extract in a separate thread. This is because packing
  526. our example MyProg.exe into a (tiny) .7z and extracting it caused a problem:
  527. GetStream and PrepareOperation and SetOperationResult were *all* called by
  528. 7-Zip from a secondary thread. So we can't block our main thread as well
  529. because then we can't communicate progress to it. Having this extra thread
  530. has the added bonus of being able to communicate progress more often from
  531. SetCompleted. }
  532. var ThreadID: TThreadID; { Not used but BeginThread requires it }
  533. const ThreadHandle = BeginThread(nil, 0, ExtractThreadFunc, Self, 0, ThreadID);
  534. if ThreadHandle = 0 then
  535. SevenZipWin32Error('BeginThread');
  536. try
  537. try
  538. while True do begin
  539. case WaitForSingleObject(ThreadHandle, 50) of
  540. WAIT_OBJECT_0: Break;
  541. WAIT_TIMEOUT: HandleProgress; { This calls the user's OnExtractionProgress handler! }
  542. WAIT_FAILED: SevenZipWin32Error('WaitForSingleObject');
  543. else
  544. SevenZipError('WaitForSingleObject returned unknown value');
  545. end;
  546. end;
  547. except
  548. { If an exception was raised during the loop (most likely it would
  549. be from the user's OnExtractionProgress handler), request abort
  550. and make one more attempt to wait on the thread. If we don't get
  551. definitive confirmation that the thread terminated (WAIT_OBJECT_0),
  552. then bump the object's reference count to prevent it from being
  553. freed, because the thread could still be running and accessing the
  554. object. Leaking memory isn't ideal, but a use-after-free problem
  555. is worse. Realisitically, though, WaitForSingleObject should never
  556. fail if given a valid handle. }
  557. FAbort := True; { Atomic so no lock }
  558. if WaitForSingleObject(ThreadHandle, INFINITE) <> WAIT_OBJECT_0 then
  559. _AddRef;
  560. raise;
  561. end;
  562. finally
  563. CloseHandle(ThreadHandle);
  564. end;
  565. HandleProgress;
  566. HandleResult;
  567. end;
  568. procedure TArchiveExtractBaseCallback.HandleResult;
  569. procedure BadOperationResultError(const opRes: TNOperationResult);
  570. begin
  571. var LogMessage: String;
  572. case opRes of
  573. kUnsupportedMethod: LogMessage := 'Unsupported method';
  574. kDataError: LogMessage := 'Data error';
  575. kCRCError: LogMessage := 'CRC error';
  576. kUnavailable: LogMessage := 'Unavailable data';
  577. kUnexpectedEnd: LogMessage := 'Unexpected end';
  578. kDataAfterEnd: LogMessage := 'Data after end';
  579. kIsNotArc: LogMessage := 'Is not an archive';
  580. kHeadersError: LogMessage := 'Headers error';
  581. kWrongPassword: LogMessage := 'Wrong password';
  582. else
  583. LogMessage := Format('Unknown operation result: %d', [Ord(opRes)]);
  584. end;
  585. case opRes of
  586. kUnsupportedMethod:
  587. SevenZipError(SetupMessages[msgArchiveUnsupportedFormat], LogMessage);
  588. kDataError, kCRCError, kUnavailable, kUnexpectedEnd, kDataAfterEnd, kIsNotArc, kHeadersError:
  589. SevenZipError(SetupMessages[msgArchiveIsCorrupted], LogMessage);
  590. kWrongPassword:
  591. SevenZipError(SetupMessages[msgArchiveIncorrectPassword], LogMessage);
  592. else
  593. SevenZipError(Ord(opRes).ToString, LogMessage);
  594. end;
  595. end;
  596. procedure BadResultError(const Res: HRESULT);
  597. begin
  598. if Res = E_OUTOFMEMORY then
  599. SevenZipError(Win32ErrorString(DWORD(E_OUTOFMEMORY)))
  600. else
  601. SevenZipWin32Error('Extract', DWORD(FResult.Res));
  602. end;
  603. begin
  604. if Assigned(FResult.SavedFatalException) then begin
  605. var Msg: String;
  606. if FResult.SavedFatalException is Exception then
  607. Msg := (FResult.SavedFatalException as Exception).Message
  608. else
  609. Msg := FResult.SavedFatalException.ClassName;
  610. InternalErrorFmt('Worker thread terminated unexpectedly with exception: %s', [Msg]);
  611. end else begin
  612. var OpRes := FResult.OpRes;
  613. if OpRes <> kOK then
  614. BadOperationResultError(OpRes)
  615. else if FResult.Res <> S_OK then
  616. BadResultError(FResult.Res);
  617. end;
  618. end;
  619. { TArchiveExtractAllCallback }
  620. procedure TArchiveExtractAllCallback.TCurrent.SetAttrib(const AAttrib: DWORD);
  621. begin
  622. Attrib := AAttrib;
  623. HasAttrib := True;
  624. end;
  625. constructor TArchiveExtractAllCallback.Create(const InArchive: IInArchive;
  626. const numItems: UInt32; const DisableFsRedir: Boolean;
  627. const ArchiveFileName, DestDir, Password: String;
  628. const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
  629. begin
  630. inherited Create(InArchive, numItems, Password);
  631. FDisableFsRedir := DisableFsRedir;
  632. FExpandedDestDir := AddBackslash(PathExpand(DestDir));
  633. FFullPaths := FullPaths;
  634. FExtractedArchiveName := PathExtractName(ArchiveFileName);
  635. FOnExtractionProgress := OnExtractionProgress;
  636. FLogQueue := TStringList.Create;
  637. end;
  638. destructor TArchiveExtractAllCallback.Destroy;
  639. begin
  640. FLogQueue.Free;
  641. end;
  642. function TArchiveExtractAllCallback.GetIndices: TArchiveExtractBaseCallback.TArrayOfUInt32;
  643. begin
  644. SetLength(Result, 0); { No indices = extract all }
  645. end;
  646. function TArchiveExtractAllCallback.GetStream(index: UInt32;
  647. out outStream: ISequentialOutStream; askExtractMode: Int32): HRESULT;
  648. begin
  649. try
  650. if FAbort then
  651. SysUtils.Abort;
  652. var NewCurrent := Default(TCurrent);
  653. if askExtractMode = kExtract then begin
  654. var Path: String;
  655. if not GetProperty(FInArchive, index, kpidPath, Path) then
  656. Path := PathChangeExt(FExtractedArchiveName, '');
  657. var IsDir: Boolean;
  658. GetProperty(FInArchive, index, kpidIsDir, IsDir);
  659. if IsDir then begin
  660. if FFullPaths then begin
  661. NewCurrent.Path := Path + '\';
  662. if not ValidateAndCombinePath(FExpandedDestDir, Path, NewCurrent.ExpandedPath) then
  663. OleError(E_ACCESSDENIED);
  664. ForceDirectories(FDisableFsRedir, NewCurrent.ExpandedPath);
  665. end;
  666. outStream := nil;
  667. end else begin
  668. var Attrib: DWORD;
  669. if GetProperty(FInArchive, index, kpidAttrib, Attrib) then begin
  670. PosixHighDetect(Attrib);
  671. NewCurrent.SetAttrib(Attrib);
  672. end;
  673. GetProperty(FInArchive, index, kpidCTime, NewCurrent.CTime);
  674. GetProperty(FInArchive, index, kpidMTime, NewCurrent.MTime);
  675. if not FFullPaths then
  676. Path := PathExtractName(Path);
  677. NewCurrent.Path := Path;
  678. if not ValidateAndCombinePath(FExpandedDestDir, Path, NewCurrent.ExpandedPath) then
  679. OleError(E_ACCESSDENIED);
  680. ForceDirectories(FDisableFsRedir, PathExtractPath(NewCurrent.ExpandedPath));
  681. const ExistingFileAttr = GetFileAttributesRedir(FDisableFsRedir, NewCurrent.ExpandedPath);
  682. if (ExistingFileAttr <> INVALID_FILE_ATTRIBUTES) and
  683. (ExistingFileAttr and FILE_ATTRIBUTE_READONLY <> 0) then
  684. SetFileAttributesRedir(FDisableFsRedir, NewCurrent.ExpandedPath, ExistingFileAttr and not FILE_ATTRIBUTE_READONLY);
  685. const DestF = TFileRedir.Create(FDisableFsRedir, NewCurrent.ExpandedPath, fdCreateAlways, faWrite, fsNone);
  686. try
  687. var BytesLeft: UInt64;
  688. if GetProperty(FInArchive, index, kpidSize, BytesLeft) then begin
  689. { To avoid file system fragmentation, preallocate all of the bytes in the
  690. destination file }
  691. DestF.Seek(Int64(BytesLeft));
  692. DestF.Truncate;
  693. DestF.Seek(0);
  694. end;
  695. { From IArchive.h: can also set outstream to nil to tell 7zip to skip the file }
  696. outstream := TSequentialOutStream.Create(DestF);
  697. finally
  698. { TSequentialOutStream duplicates the TFile, so DestF is no longer needed }
  699. DestF.Free;
  700. end;
  701. NewCurrent.outStream := outStream;
  702. end;
  703. end;
  704. System.TMonitor.Enter(FLock);
  705. try
  706. FCurrent := NewCurrent;
  707. if NewCurrent.Path <> '' then
  708. FLogQueue.Append(NewCurrent.Path)
  709. finally
  710. System.TMonitor.Exit(FLock);
  711. end;
  712. Result := S_OK;
  713. except
  714. on E: EOleSysError do
  715. Result := E.ErrorCode;
  716. on E: EAbort do
  717. Result := E_ABORT
  718. else
  719. Result := E_FAIL;
  720. end;
  721. end;
  722. function TArchiveExtractAllCallback.SetOperationResult(opRes: TNOperationResult): HRESULT;
  723. begin
  724. { From IArchive.h: Can now can close the file, set attributes, timestamps and security information }
  725. try
  726. try
  727. Result := inherited;
  728. if Result = S_OK then begin
  729. { GetStream is the only writer to outStream and ExpandedPath and HasAttrib so we don't need a lock because of this note from
  730. IArchive.h: 7-Zip doesn't call GetStream/PrepareOperation/SetOperationResult from different threads simultaneously }
  731. if (FCurrent.outStream <> nil) and (FCurrent.CTime.HasTime or FCurrent.MTime.HasTime) then
  732. SetFileTime((FCurrent.outStream as TSequentialOutStream).FFile.Handle,
  733. @FCurrent.CTime, nil, @FCurrent.MTime);
  734. FCurrent.outStream := nil; { Like 7zMain.c close the file before setting attributes - note that 7-Zip has cleared its own reference as well already }
  735. if (FCurrent.ExpandedPath <> '') and FCurrent.HasAttrib then
  736. SetFileAttributesRedir(FDisableFsRedir, FCurrent.ExpandedPath, FCurrent.Attrib);
  737. end;
  738. finally
  739. FCurrent.outStream := nil;
  740. end;
  741. except
  742. on E: EAbort do
  743. Result := E_ABORT
  744. else
  745. Result := E_FAIL;
  746. end;
  747. end;
  748. procedure TArchiveExtractAllCallback.HandleProgress;
  749. begin
  750. var CurrentPath: String;
  751. var Progress, ProgressMax: Int64;
  752. System.TMonitor.Enter(FLock);
  753. try
  754. CurrentPath := FCurrent.Path;
  755. Progress := FProgress;
  756. ProgressMax := FProgressMax;
  757. for var S in FLogQueue do
  758. LogFmt('- %s', [S]); { Just like 7zMain.c }
  759. FLogQueue.Clear;
  760. finally
  761. System.TMonitor.Exit(FLock);
  762. end;
  763. if (CurrentPath <> '') and Assigned(FOnExtractionProgress) then
  764. if not FOnExtractionProgress(FExtractedArchiveName, CurrentPath, Progress, ProgressMax) then
  765. Abort;
  766. if DownloadTemporaryFileOrExtractArchiveProcessMessages then
  767. Application.ProcessMessages;
  768. end;
  769. { TArchiveExtractToHandleCallback }
  770. constructor TArchiveExtractToHandleCallback.Create(const InArchive: IInArchive;
  771. const numItems: UInt32; const Password: String; const Index: UInt32;
  772. const DestF: TFile; const OnExtractToHandleProgress: TOnExtractToHandleProgress;
  773. const OnExtractToHandleProgressParam: Int64);
  774. begin
  775. inherited Create(InArchive, numItems, Password);
  776. FIndex := Index;
  777. FDestF := TFile.CreateDuplicate(DestF);
  778. FOnExtractToHandleProgress := OnExtractToHandleProgress;
  779. FOnExtractToHandleProgressParam := OnExtractToHandleProgressParam;
  780. end;
  781. destructor TArchiveExtractToHandleCallback.Destroy;
  782. begin
  783. FDestF.Free;
  784. inherited;
  785. end;
  786. function TArchiveExtractToHandleCallback.GetIndices: TArchiveExtractBaseCallback.TArrayOfUInt32;
  787. begin
  788. SetLength(Result, 1);
  789. Result[0] := FIndex;
  790. end;
  791. function TArchiveExtractToHandleCallback.GetStream(index: UInt32;
  792. out outStream: ISequentialOutStream; askExtractMode: Int32): HRESULT;
  793. begin
  794. try
  795. if askExtractMode = kExtract then begin
  796. if index <> FIndex then
  797. OleError(E_INVALIDARG);
  798. var IsDir: Boolean;
  799. GetProperty(FInArchive, index, kpidIsDir, IsDir);
  800. if IsDir then
  801. OleError(E_INVALIDARG);
  802. var BytesLeft: UInt64;
  803. if GetProperty(FInArchive, index, kpidSize, BytesLeft) then begin
  804. { To avoid file system fragmentation, preallocate all of the bytes in the
  805. destination file }
  806. FDestF.Seek(Int64(BytesLeft));
  807. FDestF.Truncate;
  808. FDestF.Seek(0);
  809. end;
  810. outstream := TSequentialOutStream.Create(FDestF);
  811. end;
  812. Result := S_OK;
  813. except
  814. on E: EOleSysError do
  815. Result := E.ErrorCode;
  816. on E: EAbort do
  817. Result := E_ABORT
  818. else
  819. Result := E_FAIL;
  820. end;
  821. end;
  822. procedure TArchiveExtractToHandleCallback.HandleProgress;
  823. begin
  824. if Assigned(FOnExtractToHandleProgress) then begin
  825. var Progress: Int64;
  826. System.TMonitor.Enter(FLock);
  827. try
  828. Progress := FProgress;
  829. finally
  830. System.TMonitor.Exit(FLock);
  831. end;
  832. FOnExtractToHandleProgress(Progress-FPreviousProgress, FOnExtractToHandleProgressParam);
  833. FPreviousProgress := Progress;
  834. end;
  835. end;
  836. { Additional helper functions }
  837. type
  838. TSevenZipHandlers = TDictionary<String, TGUID>;
  839. var
  840. CreateSevenZipObject: function(const clsid, iid: TGUID; var outObject): HRESULT; stdcall;
  841. VersionBanner: String;
  842. Handlers: TSevenZipHandlers;
  843. function SevenZipDLLInit(const SevenZipLibrary: HMODULE;
  844. [ref] const VersionNumbers: TFileVersionNumbers): Boolean;
  845. begin
  846. CreateSevenZipObject := GetProcAddress(SevenZipLibrary, 'CreateObject');
  847. Result := Assigned(CreateSevenZipObject);
  848. if (VersionNumbers.MS <> 0) or (VersionNumbers.LS <> 0) then
  849. VersionBanner := Format(' %u.%.2u', [(VersionNumbers.MS shr 16) and $FFFF, VersionNumbers.MS and $FFFF])
  850. else
  851. VersionBanner := '';
  852. Handlers := TSevenZipHandlers.Create(TIStringComparer.Ordinal);
  853. Handlers.Add('.7z', CLSID_Handler7z);
  854. Handlers.Add('.zip', CLSID_HandlerZip);
  855. Handlers.Add('.gz', CLSID_HandlerGzip);
  856. Handlers.Add('.bz2', CLSID_HandlerBZip2);
  857. Handlers.Add('.xz', CLSID_HandlerXz);
  858. Handlers.Add('.tar', CLSID_HandlerTar);
  859. Handlers.Add('.rar', CLSID_HandlerRar);
  860. Handlers.Add('.iso', CLSID_HandlerIso);
  861. Handlers.Add('.msi', CLSID_HandlerCompound);
  862. Handlers.Add('.cab', CLSID_HandlerCab);
  863. Handlers.Add('.rpm', CLSID_HandlerRpm);
  864. Handlers.Add('.vhd', CLSID_HandlerVhd);
  865. Handlers.Add('.vhdx', CLSID_HandlerVhdx);
  866. Handlers.Add('.vdi', CLSID_HandlerVDI);
  867. Handlers.Add('.vmdk', CLSID_HandlerVMDK);
  868. Handlers.Add('.wim', CLSID_HandlerWim);
  869. Handlers.Add('.dmg', CLSID_HandlerDmg);
  870. Handlers.Add('.001', CLSID_HandlerSplit);
  871. end;
  872. function GetHandlerForExt(const Ext, NotFoundErrorMsg: String): TGUID;
  873. begin
  874. if not Handlers.TryGetValue(Ext, Result) then
  875. InternalError(NotFoundErrorMsg);
  876. end;
  877. function GetHandler(const Filename, NotFoundErrorMsg: String): TGUID;
  878. begin;
  879. Result := GetHandlerForExt(PathExtractExt(Filename), NotFoundErrorMsg);
  880. end;
  881. procedure MapArchiveExtensions(const DestExt, SourceExt: String);
  882. begin
  883. if (Length(DestExt) < 2) or (DestExt[1] <> '.') then
  884. InternalError('MapArchiveExtensions: Invalid DestExt');
  885. const clsid = GetHandlerForExt(SourceExt, 'MapArchiveExtensions: Invalid SourceExt');
  886. Handlers.AddOrSetValue(DestExt, clsid);
  887. end;
  888. var
  889. LoggedBanner: Boolean;
  890. procedure LogBannerOnce;
  891. begin
  892. if not LoggedBanner then begin
  893. LogFmt('%s Decoder%s : Igor Pavlov', [SetupHeader.SevenZipLibraryName, VersionBanner]); { Just like 7zMain.c }
  894. LoggedBanner := True;
  895. end;
  896. end;
  897. function OpenArchiveRedir(const DisableFsRedir: Boolean;
  898. const ArchiveFilename, Password: String; const clsid: TGUID; out numItems: UInt32): IInArchive;
  899. const
  900. DefaultScanSize: Int64 = 1 shl 23; { From Client7z.cpp }
  901. begin
  902. { CreateObject }
  903. if CreateSevenZipObject(clsid, IInArchive, Result) <> S_OK then
  904. SevenZipError(SetupMessages[msgArchiveUnsupportedFormat], 'Cannot get class object' { Just like Client7z.cpp });
  905. { Open }
  906. var F: TFile := nil; { Set to nil to silence compiler }
  907. try
  908. F := TFileRedir.Create(DisableFsRedir, ArchiveFilename, fdOpenExisting, faRead, fsRead);
  909. except
  910. on E: EFileError do
  911. SevenZipWin32Error('CreateFile', E.ErrorCode);
  912. end;
  913. const InStream: IInStream = TInStream.Create(F); { InStream now owns F }
  914. var ScanSize := DefaultScanSize;
  915. const OpenCallback: IArchiveOpenCallback = TArchiveOpenFileCallback.Create(DisableFsRedir, ArchiveFileName, Password);
  916. if Result.Open(InStream, @ScanSize, OpenCallback) <> S_OK then begin
  917. if clsid = CLSID_HandlerRar then { Try RAR5 instead of RAR4 }
  918. Exit(OpenArchiveRedir(DisableFsRedir, ArchiveFilename, Password, CLSID_HandlerRar5, numItems))
  919. else
  920. SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot open file as archive' { Just like Client7z.cpp });
  921. end;
  922. if Result.GetNumberOfItems(numItems) <> S_OK then
  923. SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot get number of items');
  924. if numItems = 1 then begin
  925. { Get inner archive stream if it exists - See OpenArchive.cpp CArchiveLink::Open
  926. Give up trying to get or open it on any error }
  927. var MainSubFile: Cardinal;
  928. var SubSeqStream: ISequentialInStream;
  929. if not GetProperty(Result, $FFFF, kpidMainSubfile, MainSubFile) or
  930. (MainSubFile <> 0) or
  931. not Supports(Result, IInArchiveGetStream) or
  932. ((Result as IInArchiveGetStream).GetStream(MainSubFile, SubSeqStream) <> S_OK) or
  933. (SubSeqStream = nil) or
  934. not Supports(SubSeqStream, IInStream) then
  935. Exit;
  936. const SubStream = SubSeqStream as IInStream;
  937. { Open inner archive }
  938. var MainSubFilePath: String;
  939. if not GetProperty(Result, MainSubFile, kpidPath, MainSubFilePath) then
  940. Exit;
  941. if MainSubFilePath = '' then
  942. MainSubFilePath := PathChangeExt(ArchiveFilename, '');
  943. var SubClsid: TGUID;
  944. try
  945. SubClsid := GetHandler(MainSubFilePath, '');
  946. except
  947. Exit;
  948. end;
  949. var SubResult: IInArchive;
  950. if CreateSevenZipObject(SubClsid, IInArchive, SubResult) <> S_OK then
  951. Exit;
  952. var SubScanSize := DefaultScanSize;
  953. const SubOpenCallback: IArchiveOpenCallback =
  954. TArchiveOpenCallbackWithStreamBackup.Create(Password, InStream); { In tests the backup of InStream wasn't needed but better safe than sorry }
  955. var SubNumItems: UInt32;
  956. if (SubResult.Open(SubStream, @SubScanSize, SubOpenCallback) <> S_OK) or
  957. (SubResult.GetNumberOfItems(SubNumItems) <> S_OK) then
  958. Exit;
  959. Result := SubResult;
  960. numItems := SubNumItems;
  961. end;
  962. end;
  963. { ExtractArchiveRedir }
  964. procedure ExtractArchiveRedir(const DisableFsRedir: Boolean;
  965. const ArchiveFilename, DestDir, Password: String;
  966. const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);
  967. begin
  968. LogArchiveExtractionModeOnce;
  969. if ArchiveFileName = '' then
  970. InternalError('ExtractArchive: Invalid ArchiveFileName value');
  971. const clsid = GetHandler(ArchiveFilename,
  972. 'ExtractArchive: Unknown ArchiveFileName extension');
  973. if DestDir = '' then
  974. InternalError('ExtractArchive: Invalid DestDir value');
  975. LogFmt('Extracting archive %s to %s. Full paths? %s', [ArchiveFileName,
  976. RemoveBackslashUnlessRoot(DestDir), SYesNo[FullPaths]]);
  977. LogBannerOnce;
  978. { Open }
  979. var numItems: UInt32;
  980. const InArchive = OpenArchiveRedir(DisableFsRedir, ArchiveFilename, Password,
  981. clsid, numItems);
  982. { Extract }
  983. const ExtractCallback: IArchiveExtractCallback =
  984. TArchiveExtractAllCallback.Create(InArchive, numItems, DisableFsRedir,
  985. ArchiveFilename, DestDir, Password, FullPaths, OnExtractionProgress);
  986. (ExtractCallback as TArchiveExtractAllCallback).Extract;
  987. Log('Everything is Ok'); { Just like 7zMain.c }
  988. end;
  989. { ArchiveFindFirstFileRedir & co }
  990. type
  991. TArchiveFindState = record
  992. InArchive: IInArchive;
  993. ExpandedDestDir, ExtractedArchiveName, Password: String;
  994. RecurseSubDirs: Boolean;
  995. currentIndex, numItems: UInt32;
  996. function GetInitialCurrentFindData(out FindData: TWin32FindData): Boolean;
  997. procedure FinishCurrentFindData(var FindData: TWin32FindData);
  998. end;
  999. TArchiveFindStates = TList<TArchiveFindState>;
  1000. var
  1001. ArchiveFindStates: TArchiveFindStates;
  1002. function TArchiveFindState.GetInitialCurrentFindData(out FindData: TWin32FindData): Boolean;
  1003. function SkipFile(const Path: String; const IsDir: Boolean): Boolean;
  1004. begin
  1005. Result := (not RecurseSubDirs and (IsDir or (PathPos('\', Path) <> 0))) or
  1006. not ValidateAndCombinePath(ExpandedDestDir, Path);
  1007. end;
  1008. begin
  1009. var Path: String;
  1010. if not GetProperty(InArchive, currentIndex, kpidPath, Path) then
  1011. Path := PathChangeExt(ExtractedArchiveName, '');
  1012. var IsDir: Boolean;
  1013. GetProperty(InArchive, currentIndex, kpidIsDir, IsDir);
  1014. Result := not SkipFile(Path, IsDir);
  1015. if Result then begin
  1016. FindData := Default(TWin32FindData);
  1017. if Length(Path) >= MAX_PATH then
  1018. InternalError('GetInitialCurrentFindData: Length(Path) >= MAX_PATH');
  1019. StrPCopy(FindData.cFileName, Path);
  1020. if IsDir then
  1021. FindData.dwFileAttributes := FindData.dwFileAttributes or FILE_ATTRIBUTE_DIRECTORY;
  1022. end;
  1023. end;
  1024. procedure TArchiveFindState.FinishCurrentFindData(var FindData: TWin32FindData);
  1025. begin
  1026. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  1027. var Attrib: DWORD;
  1028. GetProperty(InArchive, currentIndex, kpidAttrib, Attrib);
  1029. PosixHighDetect(Attrib);
  1030. FindData.dwFileAttributes := FindData.dwFileAttributes or Attrib;
  1031. GetProperty(InArchive, currentIndex, kpidCTime, FindData.ftCreationTime);
  1032. GetProperty(InArchive, currentIndex, kpidMTime, FindData.ftLastWriteTime);
  1033. var Size: UInt64;
  1034. GetProperty(InArchive, currentIndex, kpidSize, Size);
  1035. FindData.nFileSizeHigh := Int64Rec(Size).Hi;
  1036. FindData.nFileSizeLow := Int64Rec(Size).Lo;
  1037. end;
  1038. end;
  1039. function ArchiveFindFirstFileRedir(const DisableFsRedir: Boolean;
  1040. const ArchiveFilename, DestDir, Password: String; const RecurseSubDirs,
  1041. ExtractIntent: Boolean; out FindFileData: TWin32FindData): TArchiveFindHandle;
  1042. begin
  1043. LogArchiveExtractionModeOnce;
  1044. if ArchiveFileName = '' then
  1045. InternalError('ArchiveFindFirstFile: Invalid ArchiveFileName value');
  1046. const clsid = GetHandler(ArchiveFilename,
  1047. 'ArchiveFindFirstFile: Unknown ArchiveFileName extension');
  1048. LogBannerOnce;
  1049. { Open }
  1050. var State := Default(TArchiveFindState);
  1051. State.InArchive := OpenArchiveRedir(DisableFsRedir, ArchiveFilename, Password, clsid, State.numItems);
  1052. if DestDir <> '' then
  1053. State.ExpandedDestDir := AddBackslash(PathExpand(DestDir));
  1054. State.ExtractedArchiveName := PathExtractName(ArchiveFilename);
  1055. State.Password := Password;
  1056. State.RecurseSubDirs := RecurseSubDirs;
  1057. { Log start of extraction }
  1058. if ExtractIntent then begin
  1059. LogFmt('Start extracting archive %s to %s. Recurse subdirs? %s', [ArchiveFilename,
  1060. RemoveBackslashUnlessRoot(DestDir), SYesNo[RecurseSubDirs]]);
  1061. var Solid: Boolean;
  1062. if GetProperty(State.InArchive, $FFFF, kpidSolid, Solid) and Solid then
  1063. Log('Archive is solid; extraction performance may degrade');
  1064. end;
  1065. if State.numItems > 0 then begin
  1066. for var currentIndex: UInt32 := 0 to State.numItems-1 do begin
  1067. if State.GetInitialCurrentFindData(FindFileData) then begin
  1068. { Finish state }
  1069. State.currentIndex := currentIndex;
  1070. { Save state }
  1071. if ArchiveFindStates = nil then
  1072. ArchiveFindStates := TArchiveFindStates.Create;
  1073. ArchiveFindStates.Add(State);
  1074. { Finish find data & exit }
  1075. State.FinishCurrentFindData(FindFileData);
  1076. Exit(TArchiveFindHandle(UInt32(ArchiveFindStates.Count-1))); { The UInt32 cast prevents sign extension }
  1077. end;
  1078. end;
  1079. end;
  1080. Result := INVALID_HANDLE_VALUE;
  1081. end;
  1082. function CheckFindFileHandle(const FindFile: TArchiveFindHandle): Integer;
  1083. begin
  1084. Result := Integer(FindFile);
  1085. if (Result < 0) or (Result >= ArchiveFindStates.Count) or
  1086. (ArchiveFindStates[Result].InArchive = nil) then
  1087. InternalError('CheckFindFileHandle failed');
  1088. end;
  1089. function ArchiveFindNextFile(const FindFile: TArchiveFindHandle; out FindFileData: TWin32FindData): Boolean;
  1090. begin
  1091. const I = CheckFindFileHandle(FindFile);
  1092. var State := ArchiveFindStates[I];
  1093. for var currentIndex := State.currentIndex+1 to State.numItems-1 do begin
  1094. State.currentIndex := currentIndex;
  1095. if State.GetInitialCurrentFindData(FindFileData) then begin
  1096. { Update state }
  1097. ArchiveFindStates[I] := State; { This just updates currentIndex }
  1098. { Finish find data & exit }
  1099. State.FinishCurrentFindData(FindFileData);
  1100. Exit(True);
  1101. end;
  1102. end;
  1103. Result := False;
  1104. end;
  1105. function ArchiveFindClose(const FindFile: TArchiveFindHandle): Boolean;
  1106. begin
  1107. const I = CheckFindFileHandle(FindFile);
  1108. var State := ArchiveFindStates[I];
  1109. State.InArchive := nil;
  1110. ArchiveFindStates[I] := State; { This just updates InArchive }
  1111. Result := True;
  1112. end;
  1113. procedure ArchiveFindExtract(const FindFile: TArchiveFindHandle; const DestF: TFile;
  1114. const OnExtractToHandleProgress: TOnExtractToHandleProgress;
  1115. const OnExtractToHandleProgressParam: Int64);
  1116. begin
  1117. const State = ArchiveFindStates[CheckFindFileHandle(FindFile)];
  1118. var FindData: TWin32FindData;
  1119. if not State.GetInitialCurrentFindData(FindData) or
  1120. (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
  1121. InternalError('ArchiveFindExtract: Invalid current');
  1122. const ExtractCallback: IArchiveExtractCallback =
  1123. TArchiveExtractToHandleCallback.Create(State.InArchive, State.numItems,
  1124. State.Password, State.currentIndex, DestF, OnExtractToHandleProgress,
  1125. OnExtractToHandleProgressParam);
  1126. (ExtractCallback as TArchiveExtractToHandleCallback).Extract;
  1127. end;
  1128. { SevenZipDLLDeInit }
  1129. procedure SevenZipDLLDeInit;
  1130. begin
  1131. FreeAndNil(Handlers);
  1132. { ArchiveFindStates has references to 7-Zip so must be cleared before the DLL is unloaded }
  1133. FreeAndNil(ArchiveFindStates);
  1134. end;
  1135. end.