Setup.DownloadFileFunc.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. unit Setup.DownloadFileFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Installation procedures: downloading files
  8. }
  9. interface
  10. uses
  11. Shared.Int64Em, Shared.FileClass, Shared.Struct;
  12. type
  13. TOnDownloadProgress = function(const Url, BaseName: string; const Progress, ProgressMax: Int64): Boolean of object;
  14. TOnSimpleDownloadProgress = procedure(const Bytes, Param: Integer64);
  15. TOnDownloadNoProgress = function: Boolean of object;
  16. TOnSimpleDownloadNoProgress = procedure;
  17. function DownloadFile(const Url, CustomUserName, CustomPassword: String;
  18. const DestF: TFile; [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
  19. const OnSimpleDownloadProgress: TOnSimpleDownloadProgress;
  20. const OnSimpleDownloadProgressParam: Integer64;
  21. const OnSimpleDownloadNoProgress: TOnSimpleDownloadNoProgress): Int64;
  22. function DownloadTemporaryFile(const Url, BaseName: String;
  23. [ref] const Verification: TSetupFileVerification; const OnDownloadProgress: TOnDownloadProgress;
  24. const OnDownloadNoProgress: TOnDownloadNoProgress): Int64; overload;
  25. function DownloadTemporaryFile(const Url, BaseName: String;
  26. [ref] const Verification: TSetupFileVerification; const OnDownloadProgress: TOnDownloadProgress;
  27. const OnDownloadNoProgress: TOnDownloadNoProgress; out DestFile: String): Int64; overload;
  28. function DownloadTemporaryFileSize(const Url: String): Int64;
  29. function DownloadTemporaryFileDate(const Url: String): String;
  30. procedure SetDownloadTemporaryFileCredentials(const User, Pass: String);
  31. function GetISSigUrl(const Url, ISSigUrl: String): String;
  32. implementation
  33. uses
  34. Windows, Classes, Forms, SysUtils, Net.HttpClient, Net.URLClient, NetEncoding,
  35. ISSigFunc, PathFunc, SHA256,
  36. Shared.CommonFunc, Shared.SetupMessageIDs, Shared.SetupTypes,
  37. SetupLdrAndSetup.InstFunc, SetupLdrAndSetup.Messages,
  38. Setup.InstFunc, Setup.ISSigVerifyFunc, Setup.LoggingFunc, Setup.MainFunc;
  39. type
  40. THTTPDataReceiver = class
  41. private
  42. type
  43. TResult = record
  44. SavedFatalException: TObject;
  45. HTTPStatusCode: Integer;
  46. HTTPStatusText: String;
  47. FileSize: Int64;
  48. end;
  49. var
  50. FBaseName, FCleanUrl: String;
  51. FHasCredentials: Boolean;
  52. FUser, FPass: String;
  53. FDestFile: TFile;
  54. FOnDownloadProgress: TOnDownloadProgress;
  55. FOnDownloadNoProgress: TOnDownloadNoProgress;
  56. FOnSimpleDownloadProgress: TOnSimpleDownloadProgress;
  57. FOnSimpleDownloadProgressParam: Integer64;
  58. FOnSimpleDownloadNoProgress: TOnSimpleDownloadNoProgress;
  59. FLock: TObject;
  60. FProgress, FProgressMax: Int64;
  61. FProgressSet: Boolean;
  62. FLastReportedProgress: Int64;
  63. FAbort: Boolean;
  64. FResult: TResult;
  65. protected
  66. procedure DoDownload;
  67. procedure HandleProgress;
  68. procedure HandleResult(const UseSetupMessagesForErrors: Boolean);
  69. public
  70. constructor Create(const Url, CustomUser, CustomPass: String; const DestFile: TFile);
  71. destructor Destroy; override;
  72. property BaseName: String write FBaseName;
  73. property OnDownloadProgress: TOnDownloadProgress write FOnDownloadProgress;
  74. property OnDownloadNoProgress: TOnDownloadNoProgress write FOnDownloadNoProgress;
  75. property OnSimpleDownloadProgress: TOnSimpleDownloadProgress write FOnSimpleDownloadProgress;
  76. property OnSimpleDownloadProgressParam: Integer64 write FOnSimpleDownloadProgressParam;
  77. property OnSimpleDownloadNoProgress: TOnSimpleDownloadNoProgress write FOnSimpleDownloadNoProgress;
  78. property Aborted: Boolean read FAbort;
  79. property Progress: Int64 read FProgress;
  80. property ProgressMax: Int64 read FProgressMax;
  81. procedure OnReceiveData(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean);
  82. function Download(const UseSetupMessagesForErrors: Boolean): Int64;
  83. end;
  84. function GetCredentialsAndCleanUrl(const Url, CustomUser, CustomPass: String; var User, Pass, CleanUrl: String) : Boolean;
  85. begin
  86. const Uri = TUri.Create(Url); { This is a record so no need to free }
  87. if CustomUser = '' then
  88. User := TNetEncoding.URL.Decode(Uri.Username)
  89. else
  90. User := CustomUser;
  91. if CustomPass = '' then
  92. Pass := TNetEncoding.URL.Decode(Uri.Password, [TURLEncoding.TDecodeOption.PlusAsSpaces])
  93. else
  94. Pass := CustomPass;
  95. Uri.Username := '';
  96. Uri.Password := '';
  97. CleanUrl := Uri.ToString;
  98. Result := (User <> '') or (Pass <> '');
  99. if Result then
  100. LogFmt('Download is using basic authentication: %s, ***', [User])
  101. else
  102. Log('Download is not using basic authentication');
  103. end;
  104. procedure SetUserAgentAndSecureProtocols(const AHTTPClient: THTTPClient);
  105. begin
  106. AHTTPClient.UserAgent := SetupTitle + ' ' + SetupVersion;
  107. { TLS 1.2 isn't enabled by default on older versions of Windows }
  108. AHTTPClient.SecureProtocols := [THTTPSecureProtocol.TLS1, THTTPSecureProtocol.TLS11, THTTPSecureProtocol.TLS12];
  109. end;
  110. { THTTPDataReceiver }
  111. constructor THTTPDataReceiver.Create(const Url, CustomUser, CustomPass: String; const DestFile: TFile);
  112. begin
  113. inherited Create;
  114. FDestFile := DestFile;
  115. FHasCredentials := GetCredentialsAndCleanUrl(Url, CustomUser, CustomPass, FUser, FPass, FCleanUrl);
  116. FLock := TObject.Create;
  117. end;
  118. destructor THTTPDataReceiver.Destroy;
  119. begin
  120. FResult.SavedFatalException.Free;
  121. FLock.Free;
  122. inherited;
  123. end;
  124. procedure THTTPDataReceiver.OnReceiveData(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean);
  125. begin
  126. if FAbort then
  127. Abort := True;
  128. System.TMonitor.Enter(FLock);
  129. try
  130. FProgress := AReadCount;
  131. FProgressMax := AContentLength;
  132. FProgressSet := True;
  133. finally
  134. System.TMonitor.Exit(FLock);
  135. end;
  136. end;
  137. procedure THTTPDataReceiver.DoDownload;
  138. begin
  139. const HTTPClient = THTTPClient.Create; { http://docwiki.embarcadero.com/RADStudio/Rio/en/Using_an_HTTP_Client }
  140. try
  141. SetUserAgentAndSecureProtocols(HTTPClient);
  142. HTTPClient.OnReceiveData := OnReceiveData;
  143. const HandleStream = THandleStream.Create(FDestFile.Handle);
  144. try
  145. if FHasCredentials then begin
  146. const Base64 = TBase64Encoding.Create(0);
  147. try
  148. HTTPClient.CustomHeaders['Authorization'] := 'Basic ' + Base64.Encode(FUser + ':' + FPass);
  149. finally
  150. Base64.Free;
  151. end;
  152. end;
  153. const HTTPResponse = HTTPClient.Get(FCleanUrl, HandleStream);
  154. FResult.HTTPStatusCode := HTTPResponse.StatusCode;
  155. FResult.HTTPStatusText := HTTPResponse.StatusText;
  156. FResult.FileSize := HandleStream.Size;
  157. finally
  158. HandleStream.Free;
  159. end;
  160. finally
  161. HTTPClient.Free;
  162. end;
  163. end;
  164. procedure THTTPDataReceiver.HandleProgress;
  165. begin
  166. var Progress, ProgressMax: Int64;
  167. var ProgressSet: Boolean;
  168. System.TMonitor.Enter(FLock);
  169. try
  170. Progress := FProgress;
  171. ProgressMax := FProgressMax;
  172. ProgressSet := FProgressSet;
  173. finally
  174. System.TMonitor.Exit(FLock);
  175. end;
  176. try
  177. if ProgressSet then begin
  178. if Assigned(FOnDownloadProgress) then begin
  179. if not FOnDownloadProgress(FCleanUrl, FBaseName, Progress, ProgressMax) then
  180. FAbort := True; { Atomic so no lock }
  181. end else if Assigned(FOnSimpleDownloadProgress) then begin
  182. try
  183. FOnSimpleDownloadProgress(Integer64(Progress-FLastReportedProgress), FOnSimpleDownloadProgressParam);
  184. finally
  185. FLastReportedProgress := Progress;
  186. end;
  187. end;
  188. end else begin
  189. if Assigned(FOnDownloadNoProgress) then begin
  190. if not FOnDownloadNoProgress then
  191. FAbort := True; { Atomic so no lock }
  192. end else if Assigned(FOnSimpleDownloadNoProgress) then
  193. FOnSimpleDownloadNoProgress;
  194. end;
  195. except
  196. if ExceptObject is EAbort then { FOnSimpleDownload(No)Progress always uses Abort to abort }
  197. FAbort := True { Atomic so no lock }
  198. else
  199. raise;
  200. end;
  201. if DownloadTemporaryFileOrExtractArchiveProcessMessages then
  202. Application.ProcessMessages;
  203. end;
  204. procedure THTTPDataReceiver.HandleResult(const UseSetupMessagesForErrors: Boolean);
  205. begin
  206. if Assigned(FResult.SavedFatalException) then begin
  207. var Msg: String;
  208. if FResult.SavedFatalException is Exception then
  209. Msg := (FResult.SavedFatalException as Exception).Message
  210. else
  211. Msg := FResult.SavedFatalException.ClassName;
  212. InternalErrorFmt('Worker thread terminated unexpectedly with exception: %s', [Msg]);
  213. end else begin
  214. if Aborted then begin
  215. if UseSetupMessagesForErrors then
  216. raise Exception.Create(SetupMessages[msgErrorDownloadAborted])
  217. else
  218. Abort;
  219. end else if (FResult.HTTPStatusCode < 200) or (FResult.HTTPStatusCode > 299) then begin
  220. if UseSetupMessagesForErrors then
  221. raise Exception.Create(FmtSetupMessage(msgErrorDownloadFailed, [IntToStr(FResult.HTTPStatusCode), FResult.HTTPStatusText]))
  222. else
  223. raise Exception.Create(Format('%d %s', [FResult.HTTPStatusCode, FResult.HTTPStatusText]));
  224. end;
  225. end;
  226. end;
  227. function DownloadThreadFunc(Parameter: Pointer): Integer;
  228. begin
  229. const D = THTTPDataReceiver(Parameter);
  230. try
  231. D.DoDownload;
  232. except
  233. const Ex = AcquireExceptionObject;
  234. MemoryBarrier;
  235. D.FResult.SavedFatalException := Ex;
  236. end;
  237. MemoryBarrier;
  238. Result := 0;
  239. end;
  240. function THTTPDataReceiver.Download(const UseSetupMessagesForErrors: Boolean): Int64;
  241. begin
  242. var ThreadID: TThreadID;
  243. const ThreadHandle = BeginThread(nil, 0, DownloadThreadFunc, Self, 0, ThreadID);
  244. if ThreadHandle = 0 then
  245. raise Exception.Create('Failed to create download thread: ' + SysErrorMessage(GetLastError));
  246. try
  247. try
  248. while True do begin
  249. case WaitForSingleObject(ThreadHandle, 50) of
  250. WAIT_OBJECT_0: Break;
  251. WAIT_TIMEOUT: HandleProgress;
  252. WAIT_FAILED: raise Exception.Create('WaitForSingleObject failed: ' + SysErrorMessage(GetLastError));
  253. else
  254. raise Exception.Create('WaitForSingleObject returned unknown value');
  255. end;
  256. end;
  257. except
  258. { If an exception was raised during the loop (most likely it would
  259. be from the user's OnDownloadProgress handler), request abort
  260. and make one more attempt to wait on the thread. }
  261. FAbort := True; { Atomic so no lock }
  262. WaitForSingleObject(ThreadHandle, INFINITE);
  263. raise;
  264. end;
  265. finally
  266. CloseHandle(ThreadHandle);
  267. end;
  268. HandleProgress;
  269. HandleResult(UseSetupMessagesForErrors);
  270. Result := FResult.FileSize;
  271. end;
  272. function MaskPasswordInUrl(const Url: String): String;
  273. var
  274. Uri: TUri;
  275. begin
  276. Uri := TUri.Create(Url);
  277. if Uri.Password <> '' then begin
  278. Uri.Password := '***';
  279. Result := Uri.ToString;
  280. end else
  281. Result := URL;
  282. end;
  283. var
  284. DownloadTemporaryFileUser, DownloadTemporaryFilePass: String;
  285. procedure SetDownloadTemporaryFileCredentials(const User, Pass: String);
  286. begin
  287. DownloadTemporaryFileUser := User;
  288. DownloadTemporaryFilePass := Pass;
  289. end;
  290. function GetISSigUrl(const Url, ISSigUrl: String): String;
  291. begin
  292. if ISSigUrl <> '' then
  293. Result := ISSigUrl
  294. else begin
  295. const Uri = TUri.Create(Url); { This is a record so no need to free }
  296. Uri.Path := Uri.Path + ISSigExt;
  297. Result := Uri.ToString;
  298. end;
  299. end;
  300. function DownloadFile(const Url, CustomUserName, CustomPassword: String;
  301. const DestF: TFile; [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
  302. const OnSimpleDownloadProgress: TOnSimpleDownloadProgress;
  303. const OnSimpleDownloadProgressParam: Integer64;
  304. const OnSimpleDownloadNoProgress: TOnSimpleDownloadNoProgress): Int64;
  305. var
  306. HTTPDataReceiver: THTTPDataReceiver;
  307. begin
  308. if Url = '' then
  309. InternalError('DownloadFile: Invalid Url value');
  310. LogFmt('Downloading file from %s', [MaskPasswordInURL(Url)]);
  311. HTTPDataReceiver := THTTPDataReceiver.Create(Url, CustomUserName, CustomPassword, DestF);
  312. try
  313. HTTPDataReceiver.OnSimpleDownloadProgress := OnSimpleDownloadProgress;
  314. HTTPDataReceiver.OnSimpleDownloadProgressParam := OnSimpleDownloadProgressParam;
  315. HTTPDataReceiver.OnSimpleDownloadNoProgress := OnSimpleDownloadNoProgress;
  316. { Download to specified handle }
  317. Result := HTTPDataReceiver.Download(False);
  318. { Check verification if specified, otherwise check everything else we can check }
  319. if Verification.Typ <> fvNone then begin
  320. var ExpectedFileHash: TSHA256Digest;
  321. if Verification.Typ = fvHash then
  322. ExpectedFileHash := Verification.Hash
  323. else
  324. DoISSigVerify(DestF, nil, ISSigSourceFilename, False, Verification.ISSigAllowedKeys, ExpectedFileHash);
  325. const FileHash = GetSHA256OfFile(DestF);
  326. if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
  327. VerificationError(veFileHashIncorrect);
  328. Log(VerificationSuccessfulLogMessage);
  329. end else begin
  330. if HTTPDataReceiver.ProgressMax > 0 then begin
  331. if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
  332. raise Exception.Create(FmtSetupMessage(msgErrorProgress, [IntToStr(HTTPDataReceiver.Progress), IntToStr(HTTPDataReceiver.ProgressMax)]))
  333. else if HTTPDataReceiver.ProgressMax <> Result then
  334. raise Exception.Create(FmtSetupMessage(msgErrorFileSize, [IntToStr(HTTPDataReceiver.ProgressMax), IntToStr(Result)]));
  335. end;
  336. end;
  337. finally
  338. HTTPDataReceiver.Free;
  339. end;
  340. end;
  341. function DownloadTemporaryFile(const Url, BaseName: String;
  342. [ref] const Verification: TSetupFileVerification; const OnDownloadProgress: TOnDownloadProgress;
  343. const OnDownloadNoProgress: TOnDownloadNoProgress; out DestFile: String): Int64;
  344. var
  345. TempFile: String;
  346. TempF: TFile;
  347. TempFileLeftOver: Boolean;
  348. HTTPDataReceiver: THTTPDataReceiver;
  349. RetriesLeft: Integer;
  350. LastError: DWORD;
  351. begin
  352. if Url = '' then
  353. InternalError('DownloadTemporaryFile: Invalid Url value');
  354. if BaseName = '' then
  355. InternalError('DownloadTemporaryFile: Invalid BaseName value');
  356. DestFile := AddBackslash(TempInstallDir) + BaseName;
  357. LogFmt('Downloading temporary file from %s: %s', [MaskPasswordInURL(Url), DestFile]);
  358. { Does not disable FS redirection, like everything else working on the temp dir }
  359. { Prepare directory }
  360. if NewFileExists(DestFile) then begin
  361. if Verification.Typ = fvHash then begin
  362. if SHA256DigestsEqual(GetSHA256OfFile(False, DestFile), Verification.Hash) then begin
  363. Log(' File already downloaded.');
  364. Result := 0;
  365. Exit;
  366. end;
  367. end else if Verification.Typ = fvISSig then begin
  368. var ExistingFileName: String;
  369. var ExistingFileSize: Int64;
  370. var ExistingFileHash: TSHA256Digest;
  371. if ISSigVerifySignature(DestFile, GetISSigAllowedKeys(ISSigAvailableKeys, Verification.ISSigAllowedKeys),
  372. ExistingFileName, ExistingFileSize, ExistingFileHash, nil, nil, nil) then begin
  373. const DestF = TFile.Create(DestFile, fdOpenExisting, faRead, fsReadWrite);
  374. try
  375. { Not checking ExistingFileName because we can't be sure what the original filename was }
  376. if (DestF.Size = ExistingFileSize) and
  377. (SHA256DigestsEqual(GetSHA256OfFile(DestF), ExistingFileHash)) then begin
  378. Log(' File already downloaded.');
  379. Result := 0;
  380. Exit;
  381. end;
  382. finally
  383. DestF.Free;
  384. end;
  385. end;
  386. end;
  387. SetFileAttributes(PChar(DestFile), GetFileAttributes(PChar(DestFile)) and not FILE_ATTRIBUTE_READONLY);
  388. DelayDeleteFile(False, DestFile, 13, 50, 250);
  389. end else
  390. ForceDirectories(False, PathExtractPath(DestFile));
  391. { Create temporary file }
  392. TempFile := GenerateUniqueName(False, PathExtractPath(DestFile), '.tmp');
  393. TempF := TFile.Create(TempFile, fdCreateAlways, faWrite, fsNone);
  394. TempFileLeftOver := True;
  395. HTTPDataReceiver := THTTPDataReceiver.Create(Url,
  396. DownloadTemporaryFileUser, DownloadTemporaryFilePass, TempF);
  397. try
  398. HTTPDataReceiver.BaseName := BaseName;
  399. HTTPDataReceiver.OnDownloadProgress := OnDownloadProgress;
  400. HTTPDataReceiver.OnDownloadNoProgress := OnDownloadNoProgress;
  401. { To test redirects: https://jrsoftware.org/download.php/is.exe
  402. To test expired certificates: https://expired.badssl.com/
  403. To test self-signed certificates: https://self-signed.badssl.com/
  404. To test basic authentication: https://guest:[email protected]/HTTP/Basic/
  405. To test 100 MB file: https://speed.hetzner.de/100MB.bin
  406. To test 1 GB file: https://speed.hetzner.de/1GB.bin
  407. To test file without a content length: https://github.com/jrsoftware/issrc/archive/main.zip }
  408. { Download to temporary file}
  409. Result := HTTPDataReceiver.Download(True);
  410. { Check verification if specified, otherwise check everything else we can check }
  411. if Verification.Typ <> fvNone then begin
  412. var ExpectedFileHash: TSHA256Digest;
  413. if Verification.Typ = fvHash then
  414. ExpectedFileHash := Verification.Hash
  415. else
  416. DoISSigVerify(TempF, nil, DestFile, False, Verification.ISSigAllowedKeys, ExpectedFileHash);
  417. FreeAndNil(TempF);
  418. const FileHash = GetSHA256OfFile(False, TempFile);
  419. if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
  420. VerificationError(veFileHashIncorrect);
  421. Log(VerificationSuccessfulLogMessage);
  422. end else begin
  423. FreeAndNil(TempF);
  424. if HTTPDataReceiver.ProgressMax > 0 then begin
  425. if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
  426. raise Exception.Create(FmtSetupMessage(msgErrorProgress, [IntToStr(HTTPDataReceiver.Progress), IntToStr(HTTPDataReceiver.ProgressMax)]))
  427. else if HTTPDataReceiver.ProgressMax <> Result then
  428. raise Exception.Create(FmtSetupMessage(msgErrorFileSize, [IntToStr(HTTPDataReceiver.ProgressMax), IntToStr(Result)]));
  429. end;
  430. end;
  431. { Rename the temporary file to the new name now, with retries if needed }
  432. RetriesLeft := 4;
  433. while not MoveFile(PChar(TempFile), PChar(DestFile)) do begin
  434. { Couldn't rename the temporary file... }
  435. LastError := GetLastError;
  436. { Does the error code indicate that it is possibly in use? }
  437. if LastErrorIndicatesPossiblyInUse(LastError, True) then begin
  438. LogFmt(' The existing file appears to be in use (%d). ' +
  439. 'Retrying.', [LastError]);
  440. Dec(RetriesLeft);
  441. Sleep(1000);
  442. if RetriesLeft > 0 then
  443. Continue;
  444. end;
  445. { Some other error occurred, or we ran out of tries }
  446. SetLastError(LastError);
  447. Win32ErrorMsg('MoveFile'); { Throws an exception }
  448. end;
  449. TempFileLeftOver := False;
  450. finally
  451. TempF.Free;
  452. HTTPDataReceiver.Free;
  453. if TempFileLeftOver then
  454. DeleteFile(TempFile);
  455. end;
  456. end;
  457. function DownloadTemporaryFile(const Url, BaseName: String;
  458. [ref] const Verification: TSetupFileVerification; const OnDownloadProgress: TOnDownloadProgress;
  459. const OnDownloadNoProgress: TOnDownloadNoProgress): Int64;
  460. begin
  461. var DestFile: String;
  462. Result := DownloadTemporaryFile(Url, BaseName, Verification, OnDownloadProgress, OnDownloadNoProgress, DestFile);
  463. end;
  464. procedure DownloadTemporaryFileSizeAndDate(const Url: String; var FileSize: Int64; var FileDate: String);
  465. var
  466. HTTPClient: THTTPClient;
  467. HTTPResponse: IHTTPResponse;
  468. User, Pass, CleanUrl: string;
  469. HasCredentials : Boolean;
  470. Base64: TBase64Encoding;
  471. begin
  472. HTTPClient := THTTPClient.Create;
  473. Base64 := nil;
  474. try
  475. HasCredentials := GetCredentialsAndCleanUrl(Url,
  476. DownloadTemporaryFileUser, DownloadTemporaryFilePass, User, Pass, CleanUrl);
  477. if HasCredentials then begin
  478. Base64 := TBase64Encoding.Create(0);
  479. HTTPClient.CustomHeaders['Authorization'] := 'Basic ' + Base64.Encode(User + ':' + Pass);
  480. end;
  481. SetUserAgentAndSecureProtocols(HTTPClient);
  482. HTTPResponse := HTTPClient.Head(CleanUrl);
  483. if (HTTPResponse.StatusCode < 200) or (HTTPResponse.StatusCode > 299) then
  484. raise Exception.Create(FmtSetupMessage(msgErrorDownloadSizeFailed, [IntToStr(HTTPResponse.StatusCode), HTTPResponse.StatusText]))
  485. else begin
  486. FileSize := HTTPResponse.ContentLength;
  487. FileDate := HTTPResponse.LastModified;
  488. end;
  489. finally
  490. Base64.Free;
  491. HTTPClient.Free;
  492. end;
  493. end;
  494. function DownloadTemporaryFileSize(const Url: String): Int64;
  495. var
  496. FileSize: Int64;
  497. FileDate: String;
  498. begin
  499. if Url = '' then
  500. InternalError('DownloadTemporaryFileSize: Invalid Url value');
  501. LogFmt('Getting size of %s.', [MaskPasswordInUrl(Url)]);
  502. DownloadTemporaryFileSizeAndDate(Url, FileSize, FileDate);
  503. Result := FileSize;
  504. end;
  505. function DownloadTemporaryFileDate(const Url: String): String;
  506. var
  507. FileSize: Int64;
  508. FileDate: String;
  509. begin
  510. if Url = '' then
  511. InternalError('DownloadTemporaryFileDate: Invalid Url value');
  512. LogFmt('Getting last modified date of %s.', [MaskPasswordInUrl(Url)]);
  513. DownloadTemporaryFileSizeAndDate(Url, FileSize, FileDate);
  514. Result := FileDate;
  515. end;
  516. end.