Преглед на файлове

Merge branch 'main' into files-downloadarchive

Martijn Laan преди 2 месеца
родител
ревизия
96af608ad8
променени са 5 файла, в които са добавени 142 реда и са изтрити 70 реда
  1. 4 4
      ISHelp/isetup.xml
  2. 4 3
      ISHelp/isxfunc.xml
  3. 7 1
      Projects/Src/Compression.SevenZipDLLDecoder.Interfaces.pas
  4. 125 60
      Projects/Src/Compression.SevenZipDLLDecoder.pas
  5. 2 2
      whatsnew.htm

+ 4 - 4
ISHelp/isetup.xml

@@ -1749,7 +1749,7 @@ Instructs Setup to proceed to comparing time stamps (last write/modified time) i
 </flag>
 <flag name="extractarchive">
 <p>This flag instructs Setup not to copy an existing archive file, but instead to extract it. Optionally use the <tt>ExtractArchivePassword</tt> parameter to specify a password.</p>
-<p>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive, that must not be set to <tt>basic</tt>.</p>
+<p>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive, that must not be set to <tt>basic</tt>.</p>
 <p>This flag must be combined with the <tt>external</tt> and <tt>ignoreversion</tt> flags, meaning it should only be used on files private to your application, <i>never</i> on shared system files.</p>
 <p>This flag is usually combined with the <tt>recursesubdirs</tt> and <tt>createallsubdirs</tt> flags.</p>
 <p>Using a solid archive is not recommended; extraction performance may degrade depending on the solid block size.</p>
@@ -4788,7 +4788,7 @@ ExtraDiskSpaceRequired=1_048_576</pre></example>
 <p><tt>basic</tt> uses an embedded version of the "7z ANSI-C Decoder" from the LZMA SDK by Igor Pavlov, as-is, except that Unicode support and error messages were improved and that it outputs memory requirements. It only supports .7z archives that are not password-protected.</p>
 <p><tt>enhanced/nopassword</tt> internally uses 7zxr.dll from the 7-Zip source code by Igor Pavlov, as-is, except that it was recompiled, code-signed, and renamed to is7zxr.dll. Compared to <tt>basic</tt>, it has lower memory requirements for archives that contain large files but increases the size of the Setup file(s). It still only supports .7z archives that are not password-protected.</p>
 <p><tt>enhanced</tt> uses 7zxa.dll instead of 7zxr.dll, recompiled, code-signed, and renamed to is7zxa.dll. It still only supports .7z archives, but they may be password-protected.</p>
-<p><tt>full</tt> uses 7z.dll instead of 7zxa.dll, recompiled, code-signed, and renamed to is7z.dll. It supports multiple archive formats, although not as many as the original 7z.dll, to reduce its size.</p>
+<p><tt>full</tt> uses 7z.dll instead of 7zxa.dll, recompiled, code-signed, and renamed to is7z.dll. It supports multiple archive formats, although not as many as the original 7z.dll, to reduce its size. It supports multi-volume archives.</p>
 <p>The following table summarizes the differences between these methods.</p>
 <indent>
 <table>
@@ -4796,13 +4796,13 @@ ExtraDiskSpaceRequired=1_048_576</pre></example>
 <tr><td><tt>basic</tt> (default)</td><td>High for large files*</td><td>No</td><td>0 KB</td><td>.7z</td></tr>
 <tr><td><tt>enhanced/nopassword</tt></td><td>Normal</td><td>No</td><td>100 KB</td><td>.7z</td></tr>
 <tr><td><tt>enhanced</tt></td><td>Normal</td><td>Yes</td><td>123 KB</td><td>.7z</td></tr>
-<tr><td><tt>full</tt></td><td>Normal</td><td>Yes</td><td>458 KB</td><td>.7z, .zip, .gz, .bz2, .xz, .tar, .rar, .iso, .msi, .cab, .rpm, .vhd, .vhdx, .vdi, .vmdk, .wim, .dmg</td></tr>
+<tr><td><tt>full</tt></td><td>Normal</td><td>Yes</td><td>458 KB</td><td>.7z, .zip, .gz, .bz2, .xz, .tar, .rar, .iso, .msi, .cab, .rpm, .vhd, .vhdx, .vdi, .vmdk, .wim, .dmg, .001</td></tr>
 </table>
 <p>* = When extracting a file, at least enough memory will always be allocated to hold the <b>entire</b> file, regardless of the block size. For example, extracting a 1 GB file using the <tt>basic</tt> method requires at least 1 GB of RAM. Consider using a different method for .7z archives that contain large files: their memory requirements depend on the dictionary size only.</p>
 </indent>
 <p>All methods overwrite read-only files which already exist in the destination directory without prompting the user.</p>
 <p>All methods restore the following file properties from the archive, if available: creation time, last modified time, attributes.</p>
-<p>When using the <tt>full</tt> method to extract a compressed archive, such as <i>archive.tar.gz</i>, or a split multi-volume archive, such as <i>archive.7z.001</i>, the output will be the archive file itself (e.g., <i>archive.tar</i> or <i>archive.7z</i>) rather than the individual files contained within it.</p>
+<p>When using the <tt>full</tt> method to extract a compressed archive, such as <i>archive.tar.gz</i>, the output will be the archive file itself (e.g., <i>archive.tar</i>) rather than the individual files contained within it.</p>
 <p>The <tt>basic</tt> method has the following additional limitations, as written by Igor Pavlov in the LZMA SDK:</p>
 <ul>
 <li>It does not support PPMd and BZip2 methods.</li>

+ 4 - 3
ISHelp/isxfunc.xml

@@ -1910,10 +1910,11 @@ end;</pre></example>
       <function>
         <name>ExtractArchive</name>
         <prototype>procedure ExtractArchive(const ArchiveFileName, DestDir, Password: String; const FullPaths: Boolean; const OnExtractionProgress: TOnExtractionProgress);</prototype>
-        <description><p>Extracts the specified archive to the specified directory, with or without using path names.</p>
+        <description><p>Extracts the specified archive to the specified directory, with or without preserving full path names.</p>
 <p>An exception will be raised if there was an error.</p>
-<p>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
+<p>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
 <p>Set OnExtractionProgress to a function to be informed of progress, or <tt>nil</tt> otherwise.</p>
+<p>When extracting without preservering path names (i.e. FullPaths is set to False), ensure that the archive does not contain multiple files with the same name. Doing so will help avoid file-in-use errors.</p>
 <p>See <i>CodeDownloadFiles.iss</i> for an example of archive extraction using just a [Files] entry instead.</p></description>
         <remarks><p>TOnExtractionProgress is defined as:</p>
 <p><tt>TOnExtractionProgress = function(const ArchiveName, FileName: String; const Progress, ProgressMax: Int64): Boolean;</tt></p>
@@ -2724,7 +2725,7 @@ Page := CreateOutputMsgMemoPage(wpWelcome,
         <name>CreateExtractionPage</name>
         <prototype>function CreateExtractionPage(const ACaption, ADescription: String; const OnExtractionProgress: TOnExtractionProgress): TExtractionWizardPage;</prototype>
         <description><p>Creates a wizard page to extract archives and show progress.</p>
-<p>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
+<p>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the <link topic="setup_archiveextraction">ArchiveExtraction</link> [Setup] section directive.</p>
 <p>Set OnExtractionProgress to a function to be informed of progress, or <tt>nil</tt> otherwise.</p>    
 <p>Unlike the other types of wizard pages, progress pages are not displayed as part of the normal page sequence (note that there is no <tt>AfterID</tt> parameter). A progress page can only be displayed programmatically by calling its <tt>Show</tt> method.</p></description>
         <remarks><p>Call the <tt>Show</tt> method to activate and show the page. When you're finished with it, call the <tt>Hide</tt> method to revert to the previous page.</p>

+ 7 - 1
Projects/Src/Compression.SevenZipDLLDecoder.Interfaces.pas

@@ -82,6 +82,7 @@ const
   CLSID_HandlerGZip: TGUID = '{23170F69-40C1-278A-1000-000110EF0000}';
 
   { From PropID.h}
+  kpidMainSubfile = 1;
   kpidPath = 3;
   kpidName = 4;
   kpidIsDir = 6;
@@ -135,7 +136,12 @@ type
     function GetStream(const name: PChar; var inStream: IInStream): HRESULT; stdcall;
   end;
 
-    IArchiveExtractCallback = interface(IProgress)
+  IInArchiveGetStream = interface
+  ['{23170F69-40C1-278A-0000-000600400000}']
+    function GetStream(index: UInt32; var stream: ISequentialInStream ): HRESULT; stdcall;
+  end;
+
+  IArchiveExtractCallback = interface(IProgress)
   ['{23170F69-40C1-278A-0000-000600200000}']
     function GetStream(index: UInt32; out outStream: ISequentialOutStream;
       askExtractMode: Int32): HRESULT; stdcall;

+ 125 - 60
Projects/Src/Compression.SevenZipDLLDecoder.pas

@@ -93,14 +93,25 @@ type
     constructor Create(const Password: String);
   end;
 
-  TArchiveOpenCallback = class(TArchiveCallback, IArchiveOpenCallback, IArchiveOpenVolumeCallback)
-  private
-    FDisableFsRedir: Boolean;
-    FArchiveFilename: String;
+  TArchiveOpenCallback = class(TArchiveCallback, IArchiveOpenCallback)
   protected
     { IArchiveOpenCallback }
     function SetTotal(files, bytes: PUInt64): HRESULT; stdcall;
     function SetCompleted(files, bytes: PUInt64): HRESULT; stdcall;
+  end;
+
+  TArchiveOpenCallbackWithStreamBackup = class(TArchiveOpenCallback)
+  private
+    FStreamBackup: IInStream;
+  public
+    constructor Create(const Password: String; const StreamToBackup: IInStream);
+  end;
+
+  TArchiveOpenFileCallback = class(TArchiveOpenCallback, IArchiveOpenVolumeCallback)
+  private
+    FDisableFsRedir: Boolean;
+    FArchiveFilename: String;
+  protected
     { IArchiveOpenVolumeCallback - queried for by 7-Zip on IArchiveOpenCallback }
     function GetProperty(propID: PROPID; var value: OleVariant): HRESULT; stdcall;
     function GetStream(const name: PChar; var inStream: IInStream): HRESULT; stdcall;
@@ -211,6 +222,49 @@ begin
   SevenZipError(ExceptMessage, LogMessage);
 end;
 
+function GetHandler(const Filename, NotFoundErrorMsg: String): TGUID;
+begin
+  const Ext = PathExtractExt(Filename);
+  if SameText(Ext, '.7z') then
+    Result := CLSID_Handler7z
+  else if SameText(Ext, '.zip') then
+    Result := CLSID_HandlerZip
+  else if SameText(Ext, '.gz') then
+    Result := CLSID_HandlerGzip
+  else if SameText(Ext, '.bz2') then
+    Result := CLSID_HandlerBZip2
+  else if SameText(Ext, '.xz') then
+    Result := CLSID_HandlerXz
+  else if SameText(Ext, '.tar') then
+    Result := CLSID_HandlerTar
+  else if SameText(Ext, '.rar') then
+    Result := CLSID_HandlerRar
+  else if SameText(Ext, '.iso') then
+    Result := CLSID_HandlerIso
+  else if SameText(Ext, '.msi') then
+    Result := CLSID_HandlerCompound
+  else if SameText(Ext, '.cab') then
+    Result := CLSID_HandlerCab
+  else if SameText(Ext, '.rpm') then
+    Result := CLSID_HandlerRpm
+  else if SameText(Ext, '.vhd') then
+    Result := CLSID_HandlerVhd
+  else if SameText(Ext, '.vhdx') then
+    Result := CLSID_HandlerVhdx
+  else if SameText(Ext, '.vdi') then
+    Result := CLSID_HandlerVDI
+  else if SameText(Ext, '.vmdk') then
+    Result := CLSID_HandlerVMDK
+  else if SameText(Ext, '.wim') then
+    Result := CLSID_HandlerWim
+  else if SameText(Ext, '.dmg') then
+    Result := CLSID_HandlerDmg
+  else if SameText(Ext, '.001') then
+    Result := CLSID_HandlerSplit
+  else
+    InternalError(NotFoundErrorMsg);
+end;
+
 const
   varFileTime = 64; { Delphi lacks proper VT_FILETIME support }
 type
@@ -390,13 +444,6 @@ end;
 
 { TArchiveOpenCallback }
 
-constructor TArchiveOpenCallback.Create(const DisableFsRedir: Boolean; const ArchiveFilename, Password: String);
-begin
-  inherited Create(Password);
-  FDisableFsRedir := DisableFsRedir;
-  FArchiveFilename := ArchiveFilename;
-end;
-
 function TArchiveOpenCallback.SetCompleted(files,
   bytes: PUInt64): HRESULT;
 begin
@@ -409,17 +456,38 @@ begin
   Result := S_OK;
 end;
 
-function TArchiveOpenCallback.GetProperty(propID: PROPID; var value: OleVariant): HRESULT;
+{ TArchiveOpenCallbackWithStreamBackup }
+
+constructor TArchiveOpenCallbackWithStreamBackup.Create(const Password: String;
+  const StreamToBackup: IInStream);
+begin
+  inherited Create(Password);
+  FStreamBackup := StreamToBackup;
+end;
+
+{ TArchiveOpenFileCallback }
+
+constructor TArchiveOpenFileCallback.Create(const DisableFsRedir: Boolean; const ArchiveFilename,
+  Password: String);
+begin
+  inherited Create(Password);
+  FDisableFsRedir := DisableFsRedir;
+  FArchiveFilename := ArchiveFilename;
+end;
+
+function TArchiveOpenFileCallback.GetProperty(propID: PROPID; var value: OleVariant): HRESULT;
 begin
   { This is for multi-volume archives: when the archive is opened 7-Zip only receives a stream. It
     will then use this callback to find the name of the archive (like archive.7z.001) to figure out
     the name of other volumes (like archive.7z.002) }
   if propID = kpidName then
-    value := FArchiveFilename;
+    value := FArchiveFilename
+  else
+    value := Unassigned; { Note sure if this is really needed }
   Result := S_OK;
 end;
 
-function TArchiveOpenCallback.GetStream(const name: PChar; var inStream: IInStream): HRESULT;
+function TArchiveOpenFileCallback.GetStream(const name: PChar; var inStream: IInStream): HRESULT;
 begin
   { This is for multi-volume archives: after 7-Zip figures out the name of other volumes (like
     archive.7z.002) it will then use this callback to open it. The callback must either return
@@ -909,48 +977,6 @@ begin
     VersionBanner := '';
 end;
 
-function GetHandler(const Ext, NotFoundErrorMsg: String): TGUID;
-begin
-  if SameText(Ext, '.7z') then
-    Result := CLSID_Handler7z
-  else if SameText(Ext, '.zip') then
-    Result := CLSID_HandlerZip
-  else if SameText(Ext, '.gz') then
-    Result := CLSID_HandlerGzip
-  else if SameText(Ext, '.bz2') then
-    Result := CLSID_HandlerBZip2
-  else if SameText(Ext, '.xz') then
-    Result := CLSID_HandlerXz
-  else if SameText(Ext, '.tar') then
-    Result := CLSID_HandlerTar
-  else if SameText(Ext, '.rar') then
-    Result := CLSID_HandlerRar
-  else if SameText(Ext, '.iso') then
-    Result := CLSID_HandlerIso
-  else if SameText(Ext, '.msi') then
-    Result := CLSID_HandlerCompound
-  else if SameText(Ext, '.cab') then
-    Result := CLSID_HandlerCab
-  else if SameText(Ext, '.rpm') then
-    Result := CLSID_HandlerRpm
-  else if SameText(Ext, '.vhd') then
-    Result := CLSID_HandlerVhd
-  else if SameText(Ext, '.vhdx') then
-    Result := CLSID_HandlerVhdx
-  else if SameText(Ext, '.vdi') then
-    Result := CLSID_HandlerVDI
-  else if SameText(Ext, '.vmdk') then
-    Result := CLSID_HandlerVMDK
-  else if SameText(Ext, '.wim') then
-    Result := CLSID_HandlerWim
-  else if SameText(Ext, '.dmg') then
-    Result := CLSID_HandlerDmg
-  else if SameText(Ext, '.001') then
-    Result := CLSID_HandlerSplit
-  else
-    InternalError(NotFoundErrorMsg);
-end;
-
 var
   LoggedBanner: Boolean;
 
@@ -964,6 +990,8 @@ end;
 
 function OpenArchiveRedir(const DisableFsRedir: Boolean;
   const ArchiveFilename, Password: String; const clsid: TGUID; out numItems: UInt32): IInArchive;
+const
+  DefaultScanSize: Int64 = 1 shl 23; { From Client7z.cpp }
 begin
   { CreateObject }
   if CreateSevenZipObject(clsid, IInArchive, Result) <> S_OK then
@@ -977,12 +1005,47 @@ begin
     SevenZipWin32Error('CreateFile');
   end;
   const InStream: IInStream = TInStream.Create(F);
-  var ScanSize: Int64 := 1 shl 23; { From Client7z.cpp }
-  const OpenCallback: IArchiveOpenCallback = TArchiveOpenCallback.Create(DisableFsRedir, ArchiveFileName, Password);
+  var ScanSize := DefaultScanSize;
+  const OpenCallback: IArchiveOpenCallback = TArchiveOpenFileCallback.Create(DisableFsRedir, ArchiveFileName, Password);
   if Result.Open(InStream, @ScanSize, OpenCallback) <> S_OK then
     SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot open file as archive' { Just like Client7z.cpp });
   if Result.GetNumberOfItems(numItems) <> S_OK then
     SevenZipError(SetupMessages[msgArchiveIsCorrupted], 'Cannot get number of items');
+
+  if numItems = 1 then begin
+    { Get inner archive stream if it exists - See OpenArchive.cpp CArchiveLink::Open }
+    var MainSubFile: Cardinal;
+    var SubSeqStream: ISequentialInStream;
+    if not GetProperty(Result, $FFFF, kpidMainSubfile, MainSubFile) or
+       (MainSubFile <> 0) or
+       not Supports(Result, IInArchiveGetStream) or
+       ((Result as IInArchiveGetStream).GetStream(MainSubFile, SubSeqStream) <> S_OK) or
+       (SubSeqStream = nil) or
+       not Supports(SubSeqStream, IInStream) then
+      Exit;
+    const SubStream = SubSeqStream as IInStream;
+
+    { Open inner archive }
+    var MainSubFilePath: String;
+    if not GetProperty(Result, MainSubFile, kpidPath, MainSubFilePath) then
+      Exit;
+    if MainSubFilePath = '' then
+      MainSubFilePath := PathChangeExt(ArchiveFilename, '');
+
+    const SubClsid = GetHandler(MainSubFilePath, '');
+    var SubResult: IInArchive;
+    if CreateSevenZipObject(SubClsid, IInArchive, SubResult) <> S_OK then
+      Exit;
+
+    var SubScanSize := DefaultScanSize;
+    const SubOpenCallback: IArchiveOpenCallback =
+      TArchiveOpenCallbackWithStreamBackup.Create(Password, InStream); { In tests the backup of InStream wasn't needed but better safe than sorry }
+    if (SubResult.Open(SubStream, @SubScanSize, SubOpenCallback) <> S_OK) or
+       (SubResult.GetNumberOfItems(numItems) <> S_OK) then
+      Exit;
+
+    Result := SubResult;
+  end;
 end;
 
 { ExtractArchiveRedir }
@@ -995,7 +1058,7 @@ begin
 
   if ArchiveFileName = '' then
     InternalError('ExtractArchive: Invalid ArchiveFileName value');
-  const clsid = GetHandler(PathExtractExt(ArchiveFilename),
+  const clsid = GetHandler(ArchiveFilename,
     'ExtractArchive: Unknown ArchiveFileName extension');
   if DestDir = '' then
     InternalError('ExtractArchive: Invalid DestDir value');
@@ -1086,7 +1149,7 @@ begin
 
   if ArchiveFileName = '' then
     InternalError('ArchiveFindFirstFile: Invalid ArchiveFileName value');
-  const clsid = GetHandler(PathExtractExt(ArchiveFilename),
+  const clsid = GetHandler(ArchiveFilename,
     'ArchiveFindFirstFile: Unknown ArchiveFileName extension');
 
   LogBannerOnce;
@@ -1207,4 +1270,6 @@ begin
   ArchiveFindStates.Free;
 end;
 
+{ TArchiveOpenCallbackWithStreamBackup }
+
 end.

+ 2 - 2
whatsnew.htm

@@ -40,7 +40,7 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
 
 <p><a name="6.5.0"></a><span class="ver">6.5.0-dev </span><span class="date">(?)</span></p>
 <span class="head2">Improved archive extraction</span>
-<p>Support for extraction of downloaded archives has been improved. It's now possible to enable support for password-protected archives and for multiple archive formats other than .7z such as .zip. This can also be used to lower the memory requirements for extraction of .7z archives that contain large files. Additionally the <tt>[Files]</tt> section now supports archive extraction.</p>
+<p>Support for extraction of downloaded archives has been improved. It's now possible to enable support for password-protected archives,  multi-volume archives, and multiple archive formats other than .7z such as .zip. This can also be used to lower the memory requirements for extraction of .7z archives that contain large files. Additionally the <tt>[Files]</tt> section now supports archive extraction.</p>
 <p>All of this is optional and does <i>not</i> increase the size of Setup if not used.</p>
 <ul>
   <li>Updated <tt>[Setup]</tt> and <tt>[Files]</tt> section:
@@ -56,7 +56,7 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
     </li>
     <li>Added new <tt>[Files]</tt> section flag <tt>extractarchive</tt> and parameter <tt>ExtractArchivePassword</tt>, intended to enable the seamless integration and extraction of archives.
       <ul>
-        <li>The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the aforementioned <tt>ArchiveExtraction</tt> directive, that must not be set to <tt>basic</tt>.</li>
+        <li>The supported archive formats, beyond .7z, and the support for password-protected and multi-volume archives, depend on the aforementioned <tt>ArchiveExtraction</tt> directive, that must not be set to <tt>basic</tt>.</li>
         <li>Flag <tt>extractarchive</tt> must be combined with the <tt>external</tt> and <tt>ignoreversion</tt> flags. It is usually also combined with the <tt>recursesubdirs</tt> and <tt>createallsubdirs</tt> flags.</li>
         <li>Using a solid archive is not recommended; extraction performance may degrade depending on the solid block size.</li>
         <li>Archive extraction otherwise behaves the same as external file copying. For example, it supports automatic uninstallation of extracted files and can be combined with same other flags and parameters.</li>