diff --git a/Examples/CodeDownloadFiles.iss b/Examples/CodeDownloadFiles.iss index ca3fcabd..81f47178 100644 --- a/Examples/CodeDownloadFiles.iss +++ b/Examples/CodeDownloadFiles.iss @@ -1,8 +1,7 @@ ; -- CodeDownloadFiles.iss -- ; -; This script shows how the CreateDownloadPage support function can be used to -; download temporary files and archives while showing the download and extraction -; progress to the user. +; This script shows how the [Files] section can be used to download files and +; archives while showing the download and extraction progress to the user. ; ; To verify the downloaded files, this script shows two methods: ; -For innosetup-latest.exe and MyProg-ExtraReadmes.7z: using Inno Setup @@ -36,9 +35,11 @@ Name: "mykey"; RuntimeID: "def02"; \ Source: "MyProg.exe"; DestDir: "{app}" Source: "MyProg.chm"; DestDir: "{app}" Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme -; These files will be downloaded. If you include flag issigverify here the file will be verified +; These files will be downloaded using [Files] only +Source: "https://jrsoftware.org/download.php/is.exe?dontcount=1"; DestName: "innosetup-latest.exe"; DestDir: "{app}"; \ + ExternalSize: 7_000_000; Flags: external download ignoreversion issigverify +; These files will be downloaded by [Code]. If you include flag issigverify here the file will be verified ; a second time while copying. Verification while copying is efficient, except for archives. -Source: "{tmp}\innosetup-latest.exe"; DestDir: "{app}"; Flags: external ignoreversion issigverify Source: "{tmp}\MyProg-ExtraReadmes.7z"; DestDir: "{app}"; Flags: external extractarchive recursesubdirs ignoreversion Source: "{tmp}\ISCrypt.dll"; DestDir: "{app}"; Flags: external ignoreversion @@ -71,9 +72,6 @@ begin if CurPageID = wpReady then begin DownloadPage.Clear; // Use AddEx or AddExWithISSigVerify to specify a username and password - DownloadPage.AddWithISSigVerify( - 'https://jrsoftware.org/download.php/is.exe?dontcount=1', '', - 'innosetup-latest.exe', AllowedKeysRuntimeIDs); DownloadPage.AddWithISSigVerify( 'https://jrsoftware.org/download.php/myprog-extrareadmes.7z', '', 'MyProg-ExtraReadmes.7z', AllowedKeysRuntimeIDs); diff --git a/Examples/CodeDownloadFiles2.iss b/Examples/CodeDownloadFiles2.iss new file mode 100644 index 00000000..cf3966ec --- /dev/null +++ b/Examples/CodeDownloadFiles2.iss @@ -0,0 +1,101 @@ +; -- CodeDownloadFiles2.iss -- +; +; This script shows how the CreateDownloadPage support function can be used to +; download files and archives while showing the download and extraction +; progress to the user. +; +; To verify the downloaded files, this script shows two methods: +; -For innosetup-latest.exe and MyProg-ExtraReadmes.7z: using Inno Setup +; Signature Tool, the [ISSigKeys] section, and the AddWithISSigVerify support +; function +; -For iscrypt.dll: using a simple SHA256 check +; Using the Inno Setup Signature Tool has the benefit that the script does not +; need to be changed when the downloaded file changes, so any installers built +; will also keep working + +[Setup] +AppName=My Program +AppVersion=1.5 +WizardStyle=modern +DefaultDirName={autopf}\My Program +DefaultGroupName=My Program +UninstallDisplayIcon={app}\MyProg.exe +OutputDir=userdocs:Inno Setup Examples Output +;Use "ArchiveExtraction=enhanced" if your archive has a password +;Use "ArchiveExtraction=full" if your archive is not a .7z file but for example a .zip file +ArchiveExtraction=enhanced/nopassword + +[ISSigKeys] +Name: "mykey"; RuntimeID: "def02"; \ + KeyID: "def020edee3c4835fd54d85eff8b66d4d899b22a777353ca4a114b652e5e7a28"; \ + PublicX: "515dc7d6c16d4a46272ceb3d158c5630a96466ab4d948e72c2029d737c823097"; \ + PublicY: "f3c21f6b5156c52a35f6f28016ee3e31a3ded60c325b81fb7b1f88c221081a61" + +[Files] +; Place any regular files here +Source: "MyProg.exe"; DestDir: "{app}" +Source: "MyProg.chm"; DestDir: "{app}" +Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme +; These files will be downloaded. If you include flag issigverify here the file will be verified +; a second time while copying. Verification while copying is efficient, except for archives. +Source: "{tmp}\innosetup-latest.exe"; DestDir: "{app}"; Flags: external ignoreversion issigverify +Source: "{tmp}\MyProg-ExtraReadmes.7z"; DestDir: "{app}"; Flags: external extractarchive recursesubdirs ignoreversion +Source: "{tmp}\ISCrypt.dll"; DestDir: "{app}"; Flags: external ignoreversion + +[Icons] +Name: "{group}\My Program"; Filename: "{app}\MyProg.exe" + +[Code] +var + DownloadPage: TDownloadWizardPage; + AllowedKeysRuntimeIDs: TStringList; + +procedure InitializeWizard; +begin + DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil); + DownloadPage.ShowBaseNameInsteadOfUrl := True; + + // To allow all keys you can also just pass nil instead of this list to AddWithISSigVerify + AllowedKeysRuntimeIDs := TStringList.Create; + AllowedKeysRuntimeIDs.Add('def02'); +end; + +procedure DeinitializeSetup; +begin + if AllowedKeysRuntimeIDs <> nil then + AllowedKeysRuntimeIDs.Free; +end; + +function NextButtonClick(CurPageID: Integer): Boolean; +begin + if CurPageID = wpReady then begin + DownloadPage.Clear; + // Use AddEx or AddExWithISSigVerify to specify a username and password + DownloadPage.AddWithISSigVerify( + 'https://jrsoftware.org/download.php/is.exe?dontcount=1', '', + 'innosetup-latest.exe', AllowedKeysRuntimeIDs); + DownloadPage.AddWithISSigVerify( + 'https://jrsoftware.org/download.php/myprog-extrareadmes.7z', '', + 'MyProg-ExtraReadmes.7z', AllowedKeysRuntimeIDs); + DownloadPage.Add( + 'https://jrsoftware.org/download.php/iscrypt.dll?dontcount=1', + 'ISCrypt.dll', '2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc'); + DownloadPage.Show; + try + try + // Downloads the files to {tmp} + DownloadPage.Download; + Result := True; + except + if DownloadPage.AbortedByUser then + Log('Aborted by user.') + else + SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK); + Result := False; + end; + finally + DownloadPage.Hide; + end; + end else + Result := True; +end; \ No newline at end of file diff --git a/Files/Default.isl b/Files/Default.isl index 607e0225..06b967d6 100644 --- a/Files/Default.isl +++ b/Files/Default.isl @@ -285,6 +285,7 @@ AbortRetryIgnoreCancel=Cancel installation StatusClosingApplications=Closing applications... StatusCreateDirs=Creating directories... StatusExtractFiles=Extracting files... +StatusDownloadFiles=Downloading files... StatusCreateIcons=Creating shortcuts... StatusCreateIniEntries=Creating INI entries... StatusCreateRegistryEntries=Creating registry entries... @@ -338,6 +339,7 @@ ErrorChangingAttr=An error occurred while trying to change the attributes of the ErrorCreatingTemp=An error occurred while trying to create a file in the destination directory: ErrorReadingSource=An error occurred while trying to read the source file: ErrorCopying=An error occurred while trying to copy a file: +ErrorDownloading=An error occurred while trying to download a file: ErrorExtracting=An error occurred while trying to extract an archive: ErrorReplacingExistingFile=An error occurred while trying to replace the existing file: ErrorRestartReplace=RestartReplace failed: diff --git a/ISHelp/isetup.xml b/ISHelp/isetup.xml index 4f933aa2..49f42665 100644 --- a/ISHelp/isetup.xml +++ b/ISHelp/isetup.xml @@ -1555,7 +1555,8 @@ Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme

The name of the source file. The compiler will prepend the path of your installation's source directory if you do not specify a fully qualified pathname.

This can be a wildcard to specify a group of files in a single entry. When a wildcard is used, all files matching it use the same options.

-

When the flag external is specified, Source must be the full pathname of an existing file (or wildcard) on the distribution media or the user's system (e.g. "{src}\license.ini").

+

When the flag external is specified but the flag download is not, Source must be the full pathname of an existing file (or wildcard) on the distribution media or the user's system (e.g. "{src}\license.ini").

+

When the flag external is specified and the flag download is also, Source must be the URL of the file to download.

Constants may only be used when the external flag is specified, because the compiler does not do any constant translating itself.

@@ -1669,6 +1670,22 @@ ExternalSize: 1_048_576; Flags: external
 

This parameter is ignored if the extractarchive flag isn't also specified.

+ +

Specifies the URL of the .issig signature file which should be downloaded, which can include constants. This file is used to verify the file downloaded from the URL specified by the Source parameter.

+

This parameter is ignored if the download and issigverify flags aren't both also specified.

+

If this parameter is not set but both these flags are used, Setup will instead append ".issig" (without quotes) to the path portion of the URL specified by the Source parameter. It will then use the result as the URL to download the .issig signature file from.

+ + + +

Specifies the basic authentication username to use for the file download, which can include constants.

+

This parameter is ignored if the download flag isn't also specified.

+ + + +

Specifies the basic authentication password to use for the file download, which can include constants. Please be aware that this password is stored in an unencrypted form in the resulting Setup file(s), even if you have enabled encryption (using the [Setup] section directive Encryption).

+

This parameter is ignored if the download flag isn't also specified.

+ +

This parameter is a set of extra options. Multiple options may be used by separating them by spaces. The following options are supported:

@@ -1712,14 +1729,23 @@ Instructs Setup to proceed to comparing time stamps (last write/modified time) i

Prevents Setup from verifying the file checksum after extraction. Use this flag on files you wish to modify while already compiled into Setup.

Must be combined with nocompression.

+ +

This flag instructs Setup not to copy an existing file, but instead to download it. Optionally use the DownloadUserName and DownloadPassword parameters to specify a basic authentication username and password.

+

This flag must be combined with the DestName and ExternalSize parameters.

+

This flag also must be combined with the external and ignoreversion flags, meaning it should only be used on files private to your application, never on shared system files.

+

This flag cannot be combined with the comparetimestamp, extractarchive, recursesubdirs, and skipifsourcedoesntexist flags.

+

Supports HTTPS (but not expired or self-signed certificates) and HTTP. Redirects are automatically followed and proxy settings are automatically used. Safe to use from services.

+

This flag instructs Inno Setup not to statically compile the file specified by the Source parameter into the installation files, but instead to copy from an existing file on the distribution media or the user's system. See the Source parameter description for more information.

+

When combined with the download or extractarchive flags, Setup does not copy the file, but instead downloads or extracts it.

-

This flag instructs Inno Setup not to copy an existing archive file, but instead to extract it. Optionally use the ExtractArchivePassword parameter to specify a password.

+

This flag instructs Setup not to copy an existing archive file, but instead to extract it. Optionally use the ExtractArchivePassword parameter to specify a password.

The supported archive formats, beyond .7z, and the support for password-protected archives, depend on the ArchiveExtraction [Setup] section directive, that must not be set to basic.

This flag must be combined with the external and ignoreversion flags, meaning it should only be used on files private to your application, never on shared system files.

This flag is usually combined with the recursesubdirs and createallsubdirs flags.

+

This flag cannot be combined with download.

Using a solid archive is not recommended; extraction performance may degrade depending on the solid block size.

@@ -1737,7 +1763,7 @@ Instructs Setup to proceed to comparing time stamps (last write/modified time) i

Instructs the compiler or Setup to verify the source file's signature using a key from the [ISSigKeys] section, allowing all keys by default. Use the ISSigAllowedKeys parameter to limit the allowed keys.

-

The verification requires an .issig signature file to be present in the same directory as the source file, created using the Inno Setup Signature Tool.

+

The verification requires an .issig signature file to be present in the same directory as the source file, created using the Inno Setup Signature Tool. If flag download is set then the .issig signature file will be downloaded instead. See the DownloadISSigSource parameter description for more information..

The precise effect of this flag depends on whether it is combined with the external flag:

    When used without the external flag, the compiler will verify the source file while it is being compressed/stored into the resulting installer. If the verification fails, compilation will abort.

    diff --git a/ISHelp/isxfunc.xml b/ISHelp/isxfunc.xml index eed631d7..0504edf1 100644 --- a/ISHelp/isxfunc.xml +++ b/ISHelp/isxfunc.xml @@ -1862,7 +1862,7 @@ end;
DownloadTemporaryFileWithISSigVerify function DownloadTemporaryFileWithISSigVerify(const Url, ISSigUrl, BaseName: String; const AllowedKeysRuntimeIDs: TStringList; const OnDownloadProgress: TOnDownloadProgress): Int64;

Like DownloadTemporaryFile, but downloads an .issig signature file first from the specified second URL and uses it to verify the main file downloaded from the first URL.

-

If the second URL is an empty string, Setup will instead append ".issig" (without quotes) to the path portion of the first URL and use the result as the URL to download the .issig signature file from.

+

If the second URL is an empty string, Setup will instead append ".issig" (without quotes) to the path portion of the first URL. It will then use the result as the URL to download the .issig signature file from.

Verification uses the specified allowed keys, looked up using [ISSigKeys] section parameter RuntimeID. To allow all keys set AllowedKeysRuntimeIDs to nil.

An exception will be raised if there was an error. Otherwise, returns the number of bytes downloaded for the main file from the first URL. Returns 0 if the main file was already downloaded and still verified.

DownloadTemporaryFile
diff --git a/Projects/Src/Compiler.CompressionHandler.pas b/Projects/Src/Compiler.CompressionHandler.pas index bd70cf72..8f23a690 100644 --- a/Projects/Src/Compiler.CompressionHandler.pas +++ b/Projects/Src/Compiler.CompressionHandler.pas @@ -215,10 +215,8 @@ begin FChunkStartOffset := FDestFile.Position.Lo - FSliceBaseOffset; FDestFile.WriteBuffer(ZLIBID, SizeOf(ZLIBID)); Dec(FSliceBytesLeft, SizeOf(ZLIBID)); - FChunkBytesRead.Hi := 0; - FChunkBytesRead.Lo := 0; - FChunkBytesWritten.Hi := 0; - FChunkBytesWritten.Lo := 0; + FChunkBytesRead := To64(0); + FChunkBytesWritten := To64(0); FInitialBytesCompressedSoFar := FCompiler.GetBytesCompressedSoFar; SelectCompressor; diff --git a/Projects/Src/Compiler.Messages.pas b/Projects/Src/Compiler.Messages.pas index 1230ad9f..044734f4 100644 --- a/Projects/Src/Compiler.Messages.pas +++ b/Projects/Src/Compiler.Messages.pas @@ -223,6 +223,7 @@ const SCompilerParamFlagMissing = 'Flag "%s" must be used if flag "%s" is used'; SCompilerParamFlagMissing2 = 'Flag "%s" must be used if parameter "%s" is used'; SCompilerParamFlagMissing3 = 'Flag "%s" must be used if flags "%s" and "%s" are both used'; + SCompilerParamFlagMissingParam = 'Parameter "%s" must be specified if flag "%s" is used'; { Types, components, tasks, check, beforeinstall, afterinstall } SCompilerParamUnknownType = 'Parameter "%s" includes an unknown type'; @@ -290,8 +291,6 @@ const SCompilerFilesWildcardNotMatched = 'No files found matching "%s"'; SCompilerFilesDestNameCantBeSpecified = 'Parameter "DestName" cannot be specified if ' + 'the "Source" parameter contains wildcards or flag "extractarchive" is used'; - SCompilerFilesStrongAssemblyNameMustBeSpecified = 'Parameter "StrongAssemblyName" must be specified if ' + - 'the flag "gacinstall" is used'; SCompilerFilesCantHaveNonExternalExternalSize = 'Parameter "ExternalSize" may only be used when ' + 'the "external" flag is used'; SCompilerFilesExcludeTooLong = 'Parameter "Excludes" contains a pattern that is too long'; diff --git a/Projects/Src/Compiler.SetupCompiler.pas b/Projects/Src/Compiler.SetupCompiler.pas index 5e2886e4..d9e36f7d 100644 --- a/Projects/Src/Compiler.SetupCompiler.pas +++ b/Projects/Src/Compiler.SetupCompiler.pas @@ -4668,8 +4668,9 @@ procedure TSetupCompiler.EnumFilesProc(const Line: PChar; const Ext: Integer); type TParam = (paFlags, paSource, paDestDir, paDestName, paCopyMode, paAttribs, paPermissions, paFontInstall, paExcludes, paExternalSize, paExtractArchivePassword, - paStrongAssemblyName, paISSigAllowedKeys, paComponents, paTasks, paLanguages, - paCheck, paBeforeInstall, paAfterInstall, paMinVersion, paOnlyBelowVersion); + paStrongAssemblyName, paISSigAllowedKeys, paDownloadISSigSource, paDownloadUserName, + paDownloadPassword, paComponents, paTasks, paLanguages, paCheck, paBeforeInstall, + paAfterInstall, paMinVersion, paOnlyBelowVersion); const ParamFilesSource = 'Source'; ParamFilesDestDir = 'DestDir'; @@ -4683,6 +4684,9 @@ const ParamFilesExtractArchivePassword = 'ExtractArchivePassword'; ParamFilesStrongAssemblyName = 'StrongAssemblyName'; ParamFilesISSigAllowedKeys = 'ISSigAllowedKeys'; + ParamFilesDownloadISSigSource = 'DownloadISSigSource'; + ParamFilesDownloadUserName = 'DownloadUserName'; + ParamFilesDownloadPassword = 'DownloadPassword'; ParamInfo: array[TParam] of TParamInfo = ( (Name: ParamCommonFlags; Flags: []), (Name: ParamFilesSource; Flags: [piRequired, piNoEmpty, piNoQuotes]), @@ -4697,6 +4701,9 @@ const (Name: ParamFilesExtractArchivePassword; Flags: []), (Name: ParamFilesStrongAssemblyName; Flags: [piNoEmpty]), (Name: ParamFilesISSigAllowedKeys; Flags: [piNoEmpty]), + (Name: ParamFilesDownloadISSigSource; Flags: []), + (Name: ParamFilesDownloadUserName; Flags: [piNoEmpty]), + (Name: ParamFilesDownloadPassword; Flags: [piNoEmpty]), (Name: ParamCommonComponents; Flags: []), (Name: ParamCommonTasks; Flags: []), (Name: ParamCommonLanguages; Flags: []), @@ -4705,7 +4712,7 @@ const (Name: ParamCommonAfterInstall; Flags: []), (Name: ParamCommonMinVersion; Flags: []), (Name: ParamCommonOnlyBelowVersion; Flags: [])); - Flags: array[0..42] of PChar = ( + Flags: array[0..43] of PChar = ( 'confirmoverwrite', 'uninsneveruninstall', 'isreadme', 'regserver', 'sharedfile', 'restartreplace', 'deleteafterinstall', 'comparetimestamp', 'fontisnttruetype', 'regtypelib', 'external', @@ -4717,7 +4724,7 @@ const 'uninsnosharedfileprompt', 'createallsubdirs', '32bit', '64bit', 'solidbreak', 'setntfscompression', 'unsetntfscompression', 'sortfilesbyname', 'gacinstall', 'sign', 'signonce', 'signcheck', - 'issigverify', 'extractarchive'); + 'issigverify', 'download', 'extractarchive'); SignFlags: array[TFileLocationSign] of String = ( '', 'sign', 'signonce', 'signcheck'); AttribsFlags: array[0..3] of PChar = ( @@ -5204,8 +5211,7 @@ begin NoCompression := False; NoEncryption := False; SolidBreak := False; - ExternalSize.Hi := 0; - ExternalSize.Lo := 0; + ExternalSize := To64(0); SortFilesByName := False; Sign := fsNoSetting; @@ -5258,7 +5264,8 @@ begin 39: ApplyNewSign(Sign, fsOnce, SCompilerParamErrorBadCombo2); 40: ApplyNewSign(Sign, fsCheck, SCompilerParamErrorBadCombo2); 41: Include(Options, foISSigVerify); - 42: Include(Options, foExtractArchive); + 42: Include(Options, foDownload); + 43: Include(Options, foExtractArchive); end; { Source } @@ -5344,6 +5351,15 @@ begin Include(Options, foExternalSizePreset); end; + { DownloadISSigSource } + DownloadISSigSource := Values[paDownloadISSigSource].Data; + + { DownloadUserName } + DownloadUserName := Values[paDownloadUserName].Data; + + { DownloadPassword } + DownloadPassword := Values[paDownloadPassword].Data; + { ExtractArchivePassword } ExtractArchivePassword := Values[paExtractArchivePassword].Data; @@ -5417,7 +5433,7 @@ begin end; if (foGacInstall in Options) and (AStrongAssemblyName = '') then - AbortCompile(SCompilerFilesStrongAssemblyNameMustBeSpecified); + AbortCompileFmt(SCompilerParamFlagMissingParam, ['StrongAssemblyName', 'gacinstall']); if AStrongAssemblyName <> '' then StrongAssemblyName := AStrongAssemblyName; @@ -5431,6 +5447,25 @@ begin Excludes := AExcludes.DelimitedText; end; + if foDownload in Options then begin + if not ExternalFile then + AbortCompileFmt(SCompilerParamFlagMissing, ['external', 'download']) + else if not(foIgnoreVersion in Options) then + AbortCompileFmt(SCompilerParamFlagMissing, ['ignoreversion', 'download']) + else if foExtractArchive in Options then + AbortCompileFmt(SCompilerParamErrorBadCombo2, [ParamCommonFlags, 'download', 'extractarchive']) + else if foCompareTimeStamp in Options then + AbortCompileFmt(SCompilerParamErrorBadCombo2, [ParamCommonFlags, 'download', 'comparetimestamp']) + else if foSkipIfSourceDoesntExist in Options then + AbortCompileFmt(SCompilerParamErrorBadCombo2, [ParamCommonFlags, 'download', 'skipifsourcedoesntexist']) + else if RecurseSubdirs then + AbortCompileFmt(SCompilerParamErrorBadCombo2, [ParamCommonFlags, 'recursesubdirs', 'download']) + else if ADestName = '' then + AbortCompileFmt(SCompilerParamFlagMissingParam, ['DestName', 'download']) + else if not(foExternalSizePreset in Options) then + AbortCompileFmt(SCompilerParamFlagMissingParam, ['ExternalSize', 'download']); + end; + if foExtractArchive in Options then begin if not ExternalFile then AbortCompileFmt(SCompilerParamFlagMissing, ['external', 'extractarchive']); @@ -5468,7 +5503,7 @@ begin (Copy(ADestDir, 1, Length('{syswow64}')) = '{syswow64}') then WarningsList.Add(SCompilerFilesWarningSharedFileSysWow64); - SourceIsWildcard := IsWildcard(SourceWildcard); + SourceIsWildcard := not(foDownload in Options) and IsWildcard(SourceWildcard); if ExternalFile then begin if RecurseSubdirs then Include(Options, foRecurseSubDirsExternal); @@ -5485,8 +5520,11 @@ begin CheckCheckOrInstall(ParamCommonCheck, Check, cikCheck); CheckCheckOrInstall(ParamCommonBeforeInstall, BeforeInstall, cikInstall); CheckCheckOrInstall(ParamCommonAfterInstall, AfterInstall, cikInstall); + CheckConst(DownloadISSigSource, MinVersion, []); + CheckConst(DownloadUserName, MinVersion, []); + CheckConst(DownloadPassword, MinVersion, []); CheckConst(ExtractArchivePassword, MinVersion, []); - end; + end; FileList := TList.Create(); DirList := TList.Create(); diff --git a/Projects/Src/Compression.SevenZipDLLDecoder.pas b/Projects/Src/Compression.SevenZipDLLDecoder.pas index 6af92a8e..1f0382d8 100644 --- a/Projects/Src/Compression.SevenZipDLLDecoder.pas +++ b/Projects/Src/Compression.SevenZipDLLDecoder.pas @@ -15,7 +15,7 @@ unit Compression.SevenZipDLLDecoder; interface uses - Windows, Shared.FileClass, Shared.VerInfoFunc, Compression.SevenZipDecoder; + Windows, Shared.FileClass, Shared.VerInfoFunc, Shared.Int64Em, Compression.SevenZipDecoder; function SevenZipDLLInit(const SevenZipLibrary: HMODULE; [ref] const VersionNumbers: TFileVersionNumbers): Boolean; @@ -33,7 +33,7 @@ procedure ExtractArchiveRedir(const DisableFsRedir: Boolean; was found. } type TArchiveFindHandle = type NativeUInt; - TOnExtractToHandleProgress = procedure(Bytes: Cardinal); + TOnExtractToHandleProgress = procedure(const Bytes, Param: Integer64); function ArchiveFindFirstFileRedir(const DisableFsRedir: Boolean; const ArchiveFilename, DestDir, Password: String; const RecurseSubDirs, ExtractIntent: Boolean; @@ -41,7 +41,7 @@ function ArchiveFindFirstFileRedir(const DisableFsRedir: Boolean; function ArchiveFindNextFile(const FindFile: TArchiveFindHandle; out FindFileData: TWin32FindData): Boolean; function ArchiveFindClose(const FindFile: TArchiveFindHandle): Boolean; procedure ArchiveFindExtract(const FindFile: TArchiveFindHandle; const DestF: TFile; - const OnExtractToHandleProgress: TOnExtractToHandleProgress); + const OnExtractToHandleProgress: TOnExtractToHandleProgress; const OnExtractToHandleProgressParam: Integer64); type TFileTimeHelper = record helper for TFileTime @@ -54,7 +54,7 @@ implementation uses Classes, SysUtils, Forms, Variants, ActiveX, ComObj, Generics.Collections, Compression.SevenZipDLLDecoder.Interfaces, PathFunc, - Shared.Int64Em, Shared.SetupMessageIDs, Shared.CommonFunc, + Shared.SetupMessageIDs, Shared.CommonFunc, SetupLdrAndSetup.Messages, SetupLdrAndSetup.RedirFunc, Setup.LoggingFunc, Setup.MainFunc, Setup.InstFunc; @@ -175,6 +175,7 @@ type FIndex: UInt32; FDestF: TFile; FOnExtractToHandleProgress: TOnExtractToHandleProgress; + FOnExtractToHandleProgressParam: Integer64; FPreviousProgress: UInt64; protected { IArchiveExtractCallback } @@ -186,7 +187,8 @@ type public constructor Create(const InArchive: IInArchive; const numItems: UInt32; const Password: String; const Index: UInt32; const DestF: TFile; - const OnExtractToHandleProgress: TOnExtractToHandleProgress); + const OnExtractToHandleProgress: TOnExtractToHandleProgress; + const OnExtractToHandleProgressParam: Integer64); end; { Helper functions } @@ -779,12 +781,14 @@ end; constructor TArchiveExtractToHandleCallback.Create(const InArchive: IInArchive; const numItems: UInt32; const Password: String; const Index: UInt32; - const DestF: TFile; const OnExtractToHandleProgress: TOnExtractToHandleProgress); + const DestF: TFile; const OnExtractToHandleProgress: TOnExtractToHandleProgress; + const OnExtractToHandleProgressParam: Integer64); begin inherited Create(InArchive, numItems, Password); FIndex := Index; FDestF := DestF; FOnExtractToHandleProgress := OnExtractToHandleProgress; + FOnExtractToHandleProgressParam := OnExtractToHandleProgressParam; end; function TArchiveExtractToHandleCallback.GetIndices: TArchiveExtractBaseCallback.TArrayOfUInt32; @@ -837,17 +841,7 @@ begin System.TMonitor.Exit(FLock); end; - var Bytes := Progress - FPreviousProgress; - while Bytes > 0 do begin - var BytesToReport: Cardinal; - if Bytes > High(BytesToReport) then - BytesToReport := High(BytesToReport) - else - BytesToReport := Bytes; - FOnExtractToHandleProgress(BytesToReport); - Dec(Bytes, BytesToReport); - end; - + FOnExtractToHandleProgress(Integer64(Progress-FPreviousProgress), FOnExtractToHandleProgressParam); FPreviousProgress := Progress; end; end; @@ -1124,7 +1118,8 @@ begin end; procedure ArchiveFindExtract(const FindFile: TArchiveFindHandle; const DestF: TFile; - const OnExtractToHandleProgress: TOnExtractToHandleProgress); + const OnExtractToHandleProgress: TOnExtractToHandleProgress; + const OnExtractToHandleProgressParam: Integer64); begin const State = ArchiveFindStates[CheckFindFileHandle(FindFile)]; @@ -1135,7 +1130,8 @@ begin const ExtractCallback: IArchiveExtractCallback = TArchiveExtractToHandleCallback.Create(State.InArchive, State.numItems, - State.Password, State.currentIndex, DestF, OnExtractToHandleProgress); + State.Password, State.currentIndex, DestF, OnExtractToHandleProgress, + OnExtractToHandleProgressParam); (ExtractCallback as TArchiveExtractToHandleCallback).Extract; end; diff --git a/Projects/Src/IDE.ScintStylerInnoSetup.pas b/Projects/Src/IDE.ScintStylerInnoSetup.pas index b64348d4..27afa35f 100644 --- a/Projects/Src/IDE.ScintStylerInnoSetup.pas +++ b/Projects/Src/IDE.ScintStylerInnoSetup.pas @@ -243,14 +243,15 @@ const FilesSectionParameters: array of TScintRawString = [ 'AfterInstall', 'Attribs', 'BeforeInstall', 'Check', 'Components', 'CopyMode', - 'DestDir', 'DestName', 'Excludes', 'ExternalSize', 'ExtractArchivePassword', + 'DestDir', 'DestName', 'DownloadISSigSource', 'DownloadPassword', + 'DownloadUserName', 'Excludes', 'ExternalSize', 'ExtractArchivePassword', 'Flags', 'FontInstall', 'ISSigAllowedKeys', 'Languages', 'MinVersion', 'OnlyBelowVersion', 'Permissions', 'Source', 'StrongAssemblyName', 'Tasks' ]; FilesSectionFlags: array of TScintRawString = [ '32bit', '64bit', 'allowunsafefiles', 'comparetimestamp', 'confirmoverwrite', - 'createallsubdirs', 'deleteafterinstall', 'dontcopy', 'dontverifychecksum', + 'createallsubdirs', 'deleteafterinstall', 'dontcopy', 'dontverifychecksum', 'download', 'external', 'extractarchive', 'fontisnttruetype', 'gacinstall', 'ignoreversion', 'isreadme', 'issigverify', 'nocompression', 'noencryption', 'noregerror', 'onlyifdestfileexists', 'onlyifdoesntexist', 'overwritereadonly', 'promptifolder', diff --git a/Projects/Src/Setup.FileExtractor.pas b/Projects/Src/Setup.FileExtractor.pas index 720e4660..743bb3be 100644 --- a/Projects/Src/Setup.FileExtractor.pas +++ b/Projects/Src/Setup.FileExtractor.pas @@ -2,7 +2,7 @@ unit Setup.FileExtractor; { Inno Setup - Copyright (C) 1997-2010 Jordan Russell + Copyright (C) 1997-2025 Jordan Russell Portions by Martijn Laan For conditions of distribution and use, see LICENSE.TXT. @@ -16,7 +16,7 @@ uses Shared.Struct, ChaCha20; type - TExtractorProgressProc = procedure(Bytes: Cardinal); + TExtractorProgressProc = procedure(const Bytes: Cardinal); TFileExtractor = class private @@ -256,8 +256,7 @@ begin FChunkLastSlice := FL.LastSlice; FChunkStartOffset := FL.StartOffset; FChunkBytesLeft := FL.ChunkCompressedSize; - FChunkDecompressedBytesRead.Hi := 0; - FChunkDecompressedBytesRead.Lo := 0; + FChunkDecompressedBytesRead := To64(0); FChunkCompressed := floChunkCompressed in FL.Flags; FChunkEncrypted := floChunkEncrypted in FL.Flags; diff --git a/Projects/Src/Setup.Install.pas b/Projects/Src/Setup.Install.pas index 8d6d4608..bbd8ce8d 100644 --- a/Projects/Src/Setup.Install.pas +++ b/Projects/Src/Setup.Install.pas @@ -12,7 +12,7 @@ unit Setup.Install; interface uses - Classes, SHA256, Shared.FileClass, Shared.SetupTypes; + Classes, SHA256, Shared.FileClass, Shared.SetupTypes, Shared.Int64Em; procedure ISSigVerifyError(const AError: TISSigVerifySignatureError; const ASigFilename: String = ''); @@ -26,9 +26,15 @@ procedure PerformInstall(var Succeeded: Boolean; const ChangesEnvironment, type TOnDownloadProgress = function(const Url, BaseName: string; const Progress, ProgressMax: Int64): Boolean of object; + TOnSimpleDownloadProgress = procedure(const Bytes, Param: Integer64); procedure ExtractTemporaryFile(const BaseName: String); function ExtractTemporaryFiles(const Pattern: String): Integer; +function DownloadFile(const Url, CustomUserName, CustomPassword: String; + const DestF: TFile; const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString; + const ISSigSourceFilename: String; + const OnSimpleDownloadProgress: TOnSimpleDownloadProgress; + const OnSimpleDownloadProgressParam: Integer64): Int64; function DownloadTemporaryFile(const Url, BaseName, RequiredSHA256OfFile: String; const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString; const OnDownloadProgress: TOnDownloadProgress): Int64; @@ -44,7 +50,7 @@ uses SetupLdrAndSetup.InstFunc, Setup.InstFunc, Setup.InstFunc.Ole, Setup.SecurityFunc, SetupLdrAndSetup.Messages, Setup.MainFunc, Setup.LoggingFunc, Setup.FileExtractor, Compression.Base, PathFunc, ISSigFunc, Shared.CommonFunc.Vcl, Compression.SevenZipDLLDecoder, - Shared.CommonFunc, SetupLdrAndSetup.RedirFunc, Shared.Int64Em, Shared.SetupMessageIDs, + Shared.CommonFunc, SetupLdrAndSetup.RedirFunc, Shared.SetupMessageIDs, Setup.WizardForm, Shared.DebugStruct, Setup.DebugClient, Shared.VerInfoFunc, Setup.ScriptRunner, Setup.RegDLL, Setup.Helper, Shared.ResUpdateFunc, Setup.DotNetFunc, TaskbarProgressFunc, NewProgressBar, RestartManager, Net.HTTPClient, Net.URLClient, NetEncoding, RegStr; @@ -73,11 +79,15 @@ begin WizardForm.FilenameLabel.Update; end; -procedure SetStatusLabelText(const S: String); +procedure SetStatusLabelText(const S: String; + const ClearFilenameLabelText: Boolean = True); begin - WizardForm.StatusLabel.Caption := S; - WizardForm.StatusLabel.Update; - SetFilenameLabelText('', True); + if WizardForm.StatusLabel.Caption <> S then begin + WizardForm.StatusLabel.Caption := S; + WizardForm.StatusLabel.Update; + end; + if ClearFilenameLabelText then + SetFilenameLabelText('', True); end; procedure InstallMessageBoxCallback(const Flags: LongInt; const After: Boolean; @@ -108,8 +118,7 @@ var CurFile: PSetupFileEntry; FileSize: Integer64; begin - InstallFilesSize.Hi := 0; - InstallFilesSize.Lo := 0; + InstallFilesSize := To64(0); AfterInstallFilesSize := InstallFilesSize; for N := 0 to Entries[seFile].Count-1 do begin CurFile := PSetupFileEntry(Entries[seFile][N]); @@ -133,8 +142,7 @@ var NewMaxValue: Integer64; begin { Calculate the MaxValue for the progress meter } - NewMaxValue.Hi := 0; - NewMaxValue.Lo := 1000 * Entries[seIcon].Count; + NewMaxValue := To64(1000 * Entries[seIcon].Count); if Entries[seIni].Count <> 0 then Inc(NewMaxValue.Lo, 1000); if Entries[seRegistry].Count <> 0 then Inc(NewMaxValue.Lo, 1000); Inc6464(NewMaxValue, InstallFilesSize); @@ -211,12 +219,30 @@ begin if NeedToAbortInstall then Abort; end; -procedure ExtractorProgressProc(Bytes: Cardinal); +procedure InternalProgressProc(const Bytes: Cardinal); begin IncProgress(Bytes); ProcessEvents; end; +procedure ExternalProgressProc64(const Bytes, MaxProgress: Integer64); +begin + var NewProgress := CurProgress; + Inc6464(NewProgress, Bytes); + { In case the source file was larger than we thought it was, stop the + progress bar at the maximum amount. Also see CopySourceFileToDestFile. } + if Compare64(NewProgress, MaxProgress) > 0 then + NewProgress := MaxProgress; + SetProgress(NewProgress); + + ProcessEvents; +end; + +procedure JustProcessEventsProc64(const Bytes, Param: Integer64); +begin + ProcessEvents; +end; + function AbortRetryIgnoreTaskDialogMsgBox(const Text: String; const RetryIgnoreAbortButtonLabels: array of String): Boolean; { Returns True if Ignore was selected, False if Retry was selected, or @@ -327,7 +353,6 @@ procedure CopySourceFileToDestFile(const SourceF, DestF: TFile; goes. Assumes file pointers of both are 0. } var BytesLeft: Integer64; - NewProgress: Integer64; BufSize: Cardinal; Buf: array[0..16383] of Byte; Context: TSHA256Context; @@ -363,15 +388,7 @@ begin if ISSigVerify then SHA256Update(Context, Buf, BufSize); - NewProgress := CurProgress; - Inc64(NewProgress, BufSize); - { In case the source file was larger than we thought it was, stop the - progress bar at the maximum amount } - if Compare64(NewProgress, MaxProgress) > 0 then - NewProgress := MaxProgress; - SetProgress(NewProgress); - - ProcessEvents; + ExternalProgressProc64(To64(BufSize), MaxProgress); end; if ISSigVerify then begin @@ -1001,7 +1018,9 @@ var AExternalFileDate should not be set External : Opposite except AExternalFileDate still not set Ext. Archive: Same as external except AExternalFileDate set and - AExternalSourceFile should be set to ArchiveFindHandle as a string } + AExternalSourceFile should be set to ArchiveFindHandle as a string + Ext. Downl. : Same as external except + AExternalSourceFile should be set to an URL } procedure InstallFont(const Filename, FontName: String; const PerUserFont, AddToFontTableNow: Boolean; var WarnedPerUserFonts: Boolean); @@ -1243,7 +1262,11 @@ var raise; end; - { Update the filename label } + { Update the status and filename labels } + if foDownload in CurFile^.Options then + SetStatusLabelText(SetupMessages[msgStatusDownloadFiles], False) + else + SetStatusLabelText(SetupMessages[msgStatusExtractFiles], False); SetFilenameLabelText(DestFile, True); LogFmt('Dest filename: %s', [DestFile]); if DisableFsRedir <> InstallDefaultDisableFsRedir then begin @@ -1268,6 +1291,7 @@ var if DestFileExistedBefore then DeleteFlags := DeleteFlags or utDeleteFile_ExistedBeforeInstall; + var CurFileDateDidRead := True; { Set to False later if needed } if Assigned(CurFileLocation) then begin if floTimeStampInUTC in CurFileLocation^.Flags then CurFileDate := CurFileLocation^.SourceTimeStamp @@ -1277,11 +1301,15 @@ var end else if Assigned(AExternalFileDate) then begin CurFileDate := AExternalFileDate^; CurFileDateValid := CurFileDate.HasTime; - end else - CurFileDateValid := GetFileDateTime(DisableFsRedir, AExternalSourceFile, CurFileDate); + end else if not(foDownload in CurFile^.Options) then + CurFileDateValid := GetFileDateTime(DisableFsRedir, AExternalSourceFile, CurFileDate) + else begin + CurFileDateValid := False; + CurFileDateDidRead := False; + end; if CurFileDateValid then LogFmt('Time stamp of our file: %s', [FileTimeToStr(CurFileDate)]) - else + else if CurFileDateDidRead then Log('Time stamp of our file: (failed to read)'); if DestFileExists then begin @@ -1303,8 +1331,10 @@ var if not(foIgnoreVersion in CurFile^.Options) then begin AllowTimeStampComparison := False; { Read version info of file being installed } + if foDownload in CurFile^.Options then + InternalError('Unexpected Download flag'); if foExtractArchive in CurFile^.Options then - InternalError('Unexpected extractarchive flag'); + InternalError('Unexpected ExtractArchive flag'); if Assigned(CurFileLocation) then begin CurFileVersionInfoValid := floVersionInfoValid in CurFileLocation^.Flags; CurFileVersionInfo.MS := CurFileLocation^.FileVersionMS; @@ -1405,6 +1435,8 @@ var { Fall back to comparing time stamps if needed } if AllowTimeStampComparison and (foCompareTimeStamp in CurFile^.Options) then begin + if foDownload in CurFile^.Options then + InternalError('Unexpected Download flag'); if not CurFileDateValid or not ExistingFileDateValid then begin { If we failed to read one of the time stamps, do the safe thing and just skip the file } @@ -1532,16 +1564,47 @@ var LastOperation := SetupMessages[msgErrorReadingSource]; if SourceFile = '' then begin { Decompress a file } - FileExtractor.SeekTo(CurFileLocation^, ExtractorProgressProc); + FileExtractor.SeekTo(CurFileLocation^, InternalProgressProc); LastOperation := SetupMessages[msgErrorCopying]; - FileExtractor.DecompressFile(CurFileLocation^, DestF, ExtractorProgressProc, + FileExtractor.DecompressFile(CurFileLocation^, DestF, InternalProgressProc, not (foDontVerifyChecksum in CurFile^.Options)); end else if foExtractArchive in CurFile^.Options then begin - { Extract a file from archive. Note: foISSigVerify for archive has + { Extract a file from archive. Note: ISSigVerify for archive has already been handled by RecurseExternalArchiveCopyFiles. } LastOperation := SetupMessages[msgErrorExtracting]; - ArchiveFindExtract(StrToInt(SourceFile), DestF, ExtractorProgressProc); + var MaxProgress := CurProgress; + Inc6464(MaxProgress, AExternalSize); + ArchiveFindExtract(StrToInt(SourceFile), DestF, ExternalProgressProc64, MaxProgress); + end + else if foDownload in CurFile^.Options then begin + { Download a file with or without ISSigVerify. Note: estimate of + extra .issig size has already been added to CurFile's ExternalSize. } + LastOperation := SetupMessages[msgErrorDownloading]; + const DownloadUserName = ExpandConst(CurFile^.DownloadUserName); + const DownloadPassword = ExpandConst(CurFile^.DownloadPassword); + var MaxProgress := CurProgress; + Inc6464(MaxProgress, AExternalSize); + if foISSigVerify in CurFile^.Options then begin + const ISSigTempFile = TempFile + ISSigExt; + const ISSigDestF = TFileRedir.Create(DisableFsRedir, ISSigTempFile, fdCreateAlways, faReadWrite, fsNone); + try + { Download the .issig file } + const ISSigUrl = GetISSigUrl(SourceFile, ExpandConst(CurFile^.DownloadISSigSource)); + DownloadFile(ISSigUrl, DownloadUserName, DownloadPassword, + ISSigDestF, False, '', '', JustProcessEventsProc64, To64(0)); + FreeAndNil(ISSigDestF); + { Download and verify the actual file } + DownloadFile(SourceFile, DownloadUserName, DownloadPassword, + DestF, True, CurFile^.ISSigAllowedKeys, TempFile, ExternalProgressProc64, MaxProgress); + finally + ISSigDestF.Free; + { Delete the .issig file } + DeleteFileRedir(DisableFsRedir, ISSigTempFile); + end; + end else + DownloadFile(SourceFile, DownloadUserName, DownloadPassword, + DestF, False, '', '', ExternalProgressProc64, MaxProgress); end else begin { Copy a duplicated non-external file, or an external file } @@ -2039,7 +2102,6 @@ var I: Integer; CurFileNumber: Integer; CurFile: PSetupFileEntry; - ExternalSize: Integer64; SourceWildcard: String; ProgressBefore, ExpectedBytesLeft: Integer64; DisableFsRedir, FoundFiles: Boolean; @@ -2078,9 +2140,7 @@ var end; if CurFile^.LocationEntry <> -1 then begin - ExternalSize.Hi := 0; { not used... } - ExternalSize.Lo := 0; - ProcessFileEntry(CurFile, DisableFsRedir, '', '', FileLocationFilenames, ExternalSize, + ProcessFileEntry(CurFile, DisableFsRedir, '', '', FileLocationFilenames, To64(0), ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll, WarnedPerUserFonts, nil); end else begin @@ -2097,7 +2157,17 @@ var repeat SetProgress(ProgressBefore); ExpectedBytesLeft := CurFile^.ExternalSize; - if foExtractArchive in CurFile^.Options then + if foDownload in CurFile^.Options then begin + if foSkipIfSourceDoesntExist in CurFile^.Options then + InternalError('Unexpected SkipIfSourceDoesntExist flag'); + if not(foCustomDestName in CurFile^.Options) then + InternalError('Expected CustomDestName flag'); + { CurFile^.DestName now includes a a filename, see TSetupCompiler.EnumFilesProc.ProcessFileList } + ProcessFileEntry(CurFile, DisableFsRedir, SourceWildcard, ExpandConst(CurFile^.DestName), + nil, ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll, + WarnedPerUserFonts, nil); + FoundFiles := True; + end else if foExtractArchive in CurFile^.Options then FoundFiles := RecurseExternalArchiveCopyFiles(DisableFsRedir, SourceWildcard, Excludes, CurFile, ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll, @@ -3609,6 +3679,8 @@ type private FBaseName, FUrl: String; FOnDownloadProgress: TOnDownloadProgress; + FOnSimpleDownloadProgress: TOnSimpleDownloadProgress; + FOnSimpleDownloadProgressParam: Integer64; FAborted: Boolean; FProgress, FProgressMax: Int64; FLastReportedProgress, FLastReportedProgressMax: Int64; @@ -3616,6 +3688,8 @@ type property BaseName: String write FBaseName; property Url: String write FUrl; property OnDownloadProgress: TOnDownloadProgress write FOnDownloadProgress; + property OnSimpleDownloadProgress: TOnSimpleDownloadProgress write FOnSimpleDownloadProgress; + property OnSimpleDownloadProgressParam: Integer64 write FOnSimpleDownloadProgressParam; property Aborted: Boolean read FAborted; property Progress: Int64 read FProgress; property ProgressMax: Int64 read FProgressMax; @@ -3644,13 +3718,24 @@ begin FLastReportedProgressMax := FProgressMax; end; end; + + if not Abort and DownloadTemporaryFileOrExtractArchiveProcessMessages then + Application.ProcessMessages; + + if Abort then + FAborted := True + end else if Assigned(FOnSimpleDownloadProgress) then begin + try + FOnSimpleDownloadProgress(Integer64(Progress-FLastReportedProgress), FOnSimpleDownloadProgressParam); + except + if ExceptObject is EAbort then begin + Abort := True; + FAborted := True; + end else + raise; + end; + FLastReportedProgress := Progress; end; - - if not Abort and DownloadTemporaryFileOrExtractArchiveProcessMessages then - Application.ProcessMessages; - - if Abort then - FAborted := True end; procedure SetUserAgentAndSecureProtocols(const AHTTPClient: THTTPClient); @@ -3708,11 +3793,92 @@ begin Result := ISSigUrl else begin const Uri = TUri.Create(Url); { This is a record so no need to free } - Uri.Path := TNetEncoding.URL.Decode(Uri.Path) + ISSigExt; + Uri.Path := Uri.Path + ISSigExt; Result := Uri.ToString; end; end; +function DownloadFile(const Url, CustomUserName, CustomPassword: String; + const DestF: TFile; const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString; + const ISSigSourceFilename: String; + const OnSimpleDownloadProgress: TOnSimpleDownloadProgress; + const OnSimpleDownloadProgressParam: Integer64): Int64; +var + HandleStream: THandleStream; + HTTPDataReceiver: THTTPDataReceiver; + HTTPClient: THTTPClient; + HTTPResponse: IHTTPResponse; + User, Pass, CleanUrl: String; + HasCredentials : Boolean; +begin + if Url = '' then + InternalError('DownloadFile: Invalid Url value'); + + LogFmt('Downloading file from %s', [MaskPasswordInURL(Url)]); + + HTTPDataReceiver := nil; + HTTPClient := nil; + HandleStream := nil; + + try + HasCredentials := GetCredentialsAndCleanUrl(URL, + CustomUserName, CustomPassword, User, Pass, CleanUrl); + + { Setup downloader } + HTTPDataReceiver := THTTPDataReceiver.Create; + HTTPDataReceiver.Url := CleanUrl; + HTTPDataReceiver.OnSimpleDownloadProgress := OnSimpleDownloadProgress; + HTTPDataReceiver.OnSimpleDownloadProgressParam := OnSimpleDownloadProgressParam; + + HTTPClient := THTTPClient.Create; { http://docwiki.embarcadero.com/RADStudio/Rio/en/Using_an_HTTP_Client } + SetUserAgentAndSecureProtocols(HTTPClient); + HTTPClient.OnReceiveData := HTTPDataReceiver.OnReceiveData; + + { Download to specified handle } + HandleStream := THandleStream.Create(DestF.Handle); + if HasCredentials then begin + const Base64 = TBase64Encoding.Create(0); + try + HTTPClient.CustomHeaders['Authorization'] := 'Basic ' + Base64.Encode(User + ':' + Pass); + finally + Base64.Free; + end; + end; + HTTPResponse := HTTPClient.Get(CleanUrl, HandleStream); + Result := 0; { silence compiler } + if HTTPDataReceiver.Aborted then + Abort + else if (HTTPResponse.StatusCode < 200) or (HTTPResponse.StatusCode > 299) then + raise Exception.Create(Format('%d %s', [HTTPResponse.StatusCode, HTTPResponse.StatusText])) + else begin + { Download completed, get size and close it } + Result := HandleStream.Size; + FreeAndNil(HandleStream); + + { Check .issig if specified, otherwise check everything else we can check } + if ISSigVerify then begin + var ExpectedFileHash: TSHA256Digest; + DoISSigVerify(DestF, nil, ISSigSourceFilename, ISSigAllowedKeys, ExpectedFileHash); + const FileHash = GetSHA256OfFile(DestF); + if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then + ISSigVerifyError(vseFileHashIncorrect, SetupMessages[msgSourceIsCorrupted]); + Log(ISSigVerificationSuccessfulLogMessage); + end else begin + if HTTPDataReceiver.ProgressMax > 0 then begin + if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then + raise Exception.Create(FmtSetupMessage(msgErrorProgress, [IntToStr(HTTPDataReceiver.Progress), IntToStr(HTTPDataReceiver.ProgressMax)])) + else if HTTPDataReceiver.ProgressMax <> Result then + raise Exception.Create(FmtSetupMessage(msgErrorFileSize, [IntToStr(HTTPDataReceiver.ProgressMax), IntToStr(Result)])); + end; + end; + end; + finally + HandleStream.Free; + HTTPClient.Free; + HTTPDataReceiver.Free; + end; +end; + function DownloadTemporaryFile(const Url, BaseName, RequiredSHA256OfFile: String; const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString; const OnDownloadProgress: TOnDownloadProgress): Int64; diff --git a/Projects/Src/Setup.MainFunc.pas b/Projects/Src/Setup.MainFunc.pas index 688153fe..89b802ce 100644 --- a/Projects/Src/Setup.MainFunc.pas +++ b/Projects/Src/Setup.MainFunc.pas @@ -1696,8 +1696,7 @@ var ComponentTypes: TStringList; I: Integer; begin - Result.Hi := 0; - Result.Lo := 0; + Result := To64(0); ComponentTypes := TStringList.Create(); for I := 0 to Entries[seComponent].Count-1 do begin @@ -1811,7 +1810,7 @@ function EnumFiles(const EnumFilesProc: TEnumFilesProc; Result := True; if foCustomDestName in CurFile^.Options then - InternalError('Unexpected custom DestName'); + InternalError('Unexpected CustomDestName flag'); const DestDir = ExpandConst(CurFile^.DestName); var FindData: TWin32FindData; @@ -1865,21 +1864,29 @@ begin end else begin { External file } - SourceWildcard := ExpandConst(CurFile^.SourceFilename); - Excludes.DelimitedText := CurFile^.Excludes; - if foExtractArchive in CurFile^.Options then begin - try - if not RecurseExternalArchiveFiles(DisableFsRedir, SourceWildcard, - Excludes, CurFile) then - Exit(False); - except on E: ESevenZipError do - { Ignore archive errors for now, will show up with proper UI during - installation } - end; - end else begin - if not RecurseExternalFiles(DisableFsRedir, PathExtractPath(SourceWildcard), '', - PathExtractName(SourceWildcard), IsWildcard(SourceWildcard), Excludes, CurFile) then + if foDownload in CurFile^.Options then begin + if not(foCustomDestName in CurFile^.Options) then + InternalError('Expected CustomDestName flag'); + { CurFile^.DestName now includes a a filename, see TSetupCompiler.EnumFilesProc.ProcessFileList } + if not EnumFilesProc(DisableFsRedir, ExpandConst(CurFile^.DestName), Param) then Exit(False); + end else begin + SourceWildcard := ExpandConst(CurFile^.SourceFilename); + Excludes.DelimitedText := CurFile^.Excludes; + if foExtractArchive in CurFile^.Options then begin + try + if not RecurseExternalArchiveFiles(DisableFsRedir, SourceWildcard, + Excludes, CurFile) then + Exit(False); + except on E: ESevenZipError do + { Ignore archive errors for now, will show up with proper UI during + installation } + end; + end else begin + if not RecurseExternalFiles(DisableFsRedir, PathExtractPath(SourceWildcard), '', + PathExtractName(SourceWildcard), IsWildcard(SourceWildcard), Excludes, CurFile) then + Exit(False); + end; end; end; end; @@ -2772,8 +2779,7 @@ var begin { Also see RecurseExternalFiles above and RecurseExternalCopyFiles in Setup.Install Also see RecurseExternalArchiveGetSizeOfFiles directly below } - Result.Hi := 0; - Result.Lo := 0; + Result := To64(0); var FindData: TWin32FindData; var H := FindFirstFileRedir(DisableFsRedir, SearchBaseDir + SearchSubDir + SearchWildcard, FindData); @@ -2821,8 +2827,7 @@ var const RecurseSubDirs: Boolean): Integer64; begin { See above } - Result.Hi := 0; - Result.Lo := 0; + Result := To64(0); var FindData: TWin32FindData; var H := ArchiveFindFirstFileRedir(DisableFsRedir, ArchiveFilename, @@ -3517,6 +3522,8 @@ begin Inc6464(MinimumSpace, PSetupFileLocationEntry(Entries[seFileLocation][LocationEntry])^.OriginalSize) end else begin if not(foExternalSizePreset in Options) then begin + if foDownload in Options then + InternalError('Unexpected download flag'); try LExcludes.DelimitedText := Excludes; if foExtractArchive in Options then begin diff --git a/Projects/Src/Setup.ScriptFunc.pas b/Projects/Src/Setup.ScriptFunc.pas index 62123dae..8df835b5 100644 --- a/Projects/Src/Setup.ScriptFunc.pas +++ b/Projects/Src/Setup.ScriptFunc.pas @@ -921,7 +921,7 @@ var Div64(FreeBytes, 1024*1024); Div64(TotalBytes, 1024*1024); end; - { Cap at 2 GB, as [Code] doesn't support 64-bit integers } + { Cap at 2 GB, as GetSpaceOnDisk doesn't use 64-bit integers } if (FreeBytes.Hi <> 0) or (FreeBytes.Lo and $80000000 <> 0) then FreeBytes.Lo := $7FFFFFFF; if (TotalBytes.Hi <> 0) or (TotalBytes.Lo and $80000000 <> 0) then diff --git a/Projects/Src/Setup.UninstallLog.pas b/Projects/Src/Setup.UninstallLog.pas index 1b112910..095a2408 100644 --- a/Projects/Src/Setup.UninstallLog.pas +++ b/Projects/Src/Setup.UninstallLog.pas @@ -2,7 +2,7 @@ unit Setup.UninstallLog; { Inno Setup - Copyright (C) 1997-2024 Jordan Russell + Copyright (C) 1997-2025 Jordan Russell Portions by Martijn Laan For conditions of distribution and use, see LICENSE.TXT. @@ -1292,8 +1292,7 @@ var EndOffset, Ofs: Integer64; CrcHeader: TUninstallCrcHeader; begin - EndOffset.Lo := Header.EndOffset; - EndOffset.Hi := 0; + EndOffset := To64(Header.EndOffset); while BufLeft = 0 do begin Ofs := F.Position; Inc64(Ofs, SizeOf(CrcHeader)); diff --git a/Projects/Src/Setup.WizardForm.pas b/Projects/Src/Setup.WizardForm.pas index df7e3942..53506db6 100644 --- a/Projects/Src/Setup.WizardForm.pas +++ b/Projects/Src/Setup.WizardForm.pas @@ -614,8 +614,7 @@ var begin ComponentEntry := PSetupComponentEntry(ComponentsList.ItemObject[Index]); - ChildrenSize.Hi := 0; - ChildrenSize.Lo := 0; + ChildrenSize := To64(0); if HasChildren then ComponentsList.EnumChildrenOf(Index, UpdateComponentSizesEnum, LongInt(@ChildrenSize)); ComponentSize := ComponentEntry.Size; @@ -637,8 +636,7 @@ var Size: Integer64; begin if shShowComponentSizes in SetupHeader.Options then begin - Size.Hi := 0; - Size.Lo := 0; + Size := To64(0); ComponentsList.EnumChildrenOf(-1, UpdateComponentSizesEnum, LongInt(@Size)); end; end; diff --git a/Projects/Src/Shared.FileClass.pas b/Projects/Src/Shared.FileClass.pas index df6c514f..3a526e2c 100644 --- a/Projects/Src/Shared.FileClass.pas +++ b/Projects/Src/Shared.FileClass.pas @@ -2,7 +2,7 @@ unit Shared.FileClass; { Inno Setup - Copyright (C) 1997-2024 Jordan Russell + Copyright (C) 1997-2025 Jordan Russell Portions by Martijn Laan For conditions of distribution and use, see LICENSE.TXT. @@ -197,12 +197,8 @@ begin end; procedure TCustomFile.Seek(Offset: Cardinal); -var - I: Integer64; begin - I.Hi := 0; - I.Lo := Offset; - Seek64(I); + Seek64(To64(Offset)); end; procedure TCustomFile.WriteAnsiString(const S: AnsiString); diff --git a/Projects/Src/Shared.Int64Em.pas b/Projects/Src/Shared.Int64Em.pas index 778e1cee..18fa2d54 100644 --- a/Projects/Src/Shared.Int64Em.pas +++ b/Projects/Src/Shared.Int64Em.pas @@ -2,7 +2,7 @@ unit Shared.Int64Em; { Inno Setup - Copyright (C) 1997-2024 Jordan Russell + Copyright (C) 1997-2025 Jordan Russell Portions by Martijn Laan For conditions of distribution and use, see LICENSE.TXT. @@ -31,6 +31,7 @@ function Mul64(var X: Integer64; N: LongWord): Boolean; procedure Multiply32x32to64(N1, N2: LongWord; var X: Integer64); procedure Shr64(var X: Integer64; Count: LongWord); function StrToInteger64(const S: String; var X: Integer64): Boolean; +function To64(const Lo: Longword): Integer64; implementation @@ -243,8 +244,7 @@ begin if (StartIndex > Len) or (S[StartIndex] = '_') then Exit; - V.Lo := 0; - V.Hi := 0; + V := To64(0); for I := StartIndex to Len do begin C := UpCase(S[I]); case C of @@ -287,4 +287,10 @@ begin SetString(Result, PChar(@Buf[I]), (High(Buf) + 1) - I); end; +function To64(const Lo: Longword): Integer64; +begin + Result.Lo := Lo; + Result.Hi := 0; +end; + end. diff --git a/Projects/Src/Shared.SetupMessageIDs.pas b/Projects/Src/Shared.SetupMessageIDs.pas index 00249c0a..b7420aa2 100644 --- a/Projects/Src/Shared.SetupMessageIDs.pas +++ b/Projects/Src/Shared.SetupMessageIDs.pas @@ -87,6 +87,7 @@ type msgErrorCreatingTemp, msgErrorDownloadAborted, msgErrorDownloadFailed, + msgErrorDownloading, msgErrorDownloadSizeFailed, msgErrorExecutingProgram, msgErrorExtracting, @@ -235,6 +236,7 @@ type msgStatusCreateIcons, msgStatusCreateIniEntries, msgStatusCreateRegistryEntries, + msgStatusDownloadFiles, msgStatusExtractFiles, msgStatusRegisterFiles, msgStatusRestartingApplications, diff --git a/Projects/Src/Shared.Struct.pas b/Projects/Src/Shared.Struct.pas index 03b42daf..4e2e17de 100644 --- a/Projects/Src/Shared.Struct.pas +++ b/Projects/Src/Shared.Struct.pas @@ -227,14 +227,14 @@ type PublicX, PublicY, RuntimeID: String; end; const - SetupFileEntryStrings = 12; + SetupFileEntryStrings = 15; SetupFileEntryAnsiStrings = 1; type PSetupFileEntry = ^TSetupFileEntry; TSetupFileEntry = packed record SourceFilename, DestName, InstallFontName, StrongAssemblyName, Components, Tasks, Languages, Check, AfterInstall, BeforeInstall, Excludes, - ExtractArchivePassword: String; + DownloadISSigSource, DownloadUserName, DownloadPassword, ExtractArchivePassword: String; ISSigAllowedKeys: AnsiString; MinVersion, OnlyBelowVersion: TSetupVersionData; LocationEntry: Integer; @@ -251,7 +251,8 @@ type foRecurseSubDirsExternal, foReplaceSameVersionIfContentsDiffer, foDontVerifyChecksum, foUninsNoSharedFilePrompt, foCreateAllSubDirs, fo32Bit, fo64Bit, foExternalSizePreset, foSetNTFSCompression, - foUnsetNTFSCompression, foGacInstall, foISSigVerify, foExtractArchive); + foUnsetNTFSCompression, foGacInstall, foISSigVerify, foDownload, + foExtractArchive); FileType: (ftUserFile, ftUninstExe); end; const diff --git a/setup.iss b/setup.iss index 4f4977e4..29c3286f 100644 --- a/setup.iss +++ b/setup.iss @@ -176,6 +176,7 @@ Source: "Examples\CodeClasses.iss"; DestDir: "{app}\Examples"; Flags: ignorevers Source: "Examples\CodeDlg.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch Source: "Examples\CodeDll.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch Source: "Examples\CodeDownloadFiles.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch +Source: "Examples\CodeDownloadFiles2.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch Source: "Examples\CodeExample1.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch Source: "Examples\CodePrepareToInstall.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch Source: "Examples\Components.iss"; DestDir: "{app}\Examples"; Flags: ignoreversion touch diff --git a/whatsnew.htm b/whatsnew.htm index a4afd45d..6429bc69 100644 --- a/whatsnew.htm +++ b/whatsnew.htm @@ -82,6 +82,26 @@ Source: "{tmp}\MyProg-ExtraReadmes.7z"; DestDir: "{app}"; \ +Improved file downloads +

Support for downloading files has been improved: the [Files] section now supports file downloads. Writing Pascal Script to download a file is no longer necessary and is in fact less efficient since it requires an intermediate temporary file which this new download support doesn't.

+ New signature-verification capability

Inno Setup now includes an integrated signature-verification capability that can be used to detect corruption or tampering in your files at compile time, before files are included in an installer being built, or during installation, before Setup copies external files onto a user's system.

Any type of file may be signed and verified, and creation of signatures does not require a certificate from a certificate authority. There is no cost involved.

@@ -184,13 +204,14 @@ issigtool --key-file="MyKey.ispublickey" verify "MyProg.dll"
  • Minor tweaks.
  • -

    Some messages have been added and changed in this version: (View differences in Default.isl.)

    +

    Some messages have been added and changed in this version: (View differences in Default.isl.)