Compression.SevenZipDLLDecoder.pas 44 KB

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