Compression.SevenZipDLLDecoder.pas 43 KB

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