Setup.DownloadFileFunc.pas 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. unit Setup.DownloadFileFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2026 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Installation procedures: downloading files
  8. }
  9. interface
  10. uses
  11. 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: Int64);
  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: Int64;
  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: Int64;
  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: Int64 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. AHTTPClient.SecureProtocols := [THTTPSecureProtocol.TLS12, THTTPSecureProtocol.TLS13];
  108. end;
  109. { THTTPDataReceiver }
  110. constructor THTTPDataReceiver.Create(const Url, CustomUser, CustomPass: String; const DestFile: TFile);
  111. begin
  112. inherited Create;
  113. FDestFile := DestFile;
  114. FHasCredentials := GetCredentialsAndCleanUrl(Url, CustomUser, CustomPass, FUser, FPass, FCleanUrl);
  115. FLock := TObject.Create;
  116. end;
  117. destructor THTTPDataReceiver.Destroy;
  118. begin
  119. FResult.SavedFatalException.Free;
  120. FLock.Free;
  121. inherited;
  122. end;
  123. procedure THTTPDataReceiver.OnReceiveData(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean);
  124. begin
  125. if FAbort then
  126. Abort := True;
  127. System.TMonitor.Enter(FLock);
  128. try
  129. FProgress := AReadCount;
  130. FProgressMax := AContentLength;
  131. FProgressSet := True;
  132. finally
  133. System.TMonitor.Exit(FLock);
  134. end;
  135. end;
  136. procedure THTTPDataReceiver.DoDownload;
  137. begin
  138. const HTTPClient = THTTPClient.Create; { http://docwiki.embarcadero.com/RADStudio/Rio/en/Using_an_HTTP_Client }
  139. try
  140. SetUserAgentAndSecureProtocols(HTTPClient);
  141. HTTPClient.OnReceiveData := OnReceiveData;
  142. const HandleStream = THandleStream.Create(FDestFile.Handle);
  143. try
  144. if FHasCredentials then begin
  145. const Base64 = TBase64Encoding.Create(0);
  146. try
  147. HTTPClient.CustomHeaders['Authorization'] := 'Basic ' + Base64.Encode(FUser + ':' + FPass);
  148. finally
  149. Base64.Free;
  150. end;
  151. end;
  152. const HTTPResponse = HTTPClient.Get(FCleanUrl, HandleStream);
  153. FResult.HTTPStatusCode := HTTPResponse.StatusCode;
  154. FResult.HTTPStatusText := HTTPResponse.StatusText;
  155. FResult.FileSize := HandleStream.Size;
  156. finally
  157. HandleStream.Free;
  158. end;
  159. finally
  160. HTTPClient.Free;
  161. end;
  162. end;
  163. procedure THTTPDataReceiver.HandleProgress;
  164. begin
  165. var Progress, ProgressMax: Int64;
  166. var ProgressSet: Boolean;
  167. System.TMonitor.Enter(FLock);
  168. try
  169. Progress := FProgress;
  170. ProgressMax := FProgressMax;
  171. ProgressSet := FProgressSet;
  172. finally
  173. System.TMonitor.Exit(FLock);
  174. end;
  175. try
  176. if ProgressSet then begin
  177. if Assigned(FOnDownloadProgress) then begin
  178. if not FOnDownloadProgress(FCleanUrl, FBaseName, Progress, ProgressMax) then
  179. FAbort := True; { Atomic so no lock }
  180. end else if Assigned(FOnSimpleDownloadProgress) then begin
  181. try
  182. FOnSimpleDownloadProgress(Progress-FLastReportedProgress, FOnSimpleDownloadProgressParam);
  183. finally
  184. FLastReportedProgress := Progress;
  185. end;
  186. end;
  187. end else begin
  188. if Assigned(FOnDownloadNoProgress) then begin
  189. if not FOnDownloadNoProgress then
  190. FAbort := True; { Atomic so no lock }
  191. end else if Assigned(FOnSimpleDownloadNoProgress) then
  192. FOnSimpleDownloadNoProgress;
  193. end;
  194. except
  195. if ExceptObject is EAbort then { FOnSimpleDownload(No)Progress always uses Abort to abort }
  196. FAbort := True { Atomic so no lock }
  197. else
  198. raise;
  199. end;
  200. if DownloadTemporaryFileOrExtractArchiveProcessMessages then
  201. Application.ProcessMessages;
  202. end;
  203. procedure THTTPDataReceiver.HandleResult(const UseSetupMessagesForErrors: Boolean);
  204. begin
  205. if Assigned(FResult.SavedFatalException) then begin
  206. var Msg: String;
  207. if FResult.SavedFatalException is Exception then
  208. Msg := (FResult.SavedFatalException as Exception).Message
  209. else
  210. Msg := FResult.SavedFatalException.ClassName;
  211. InternalErrorFmt('Worker thread terminated unexpectedly with exception: %s', [Msg]);
  212. end else begin
  213. if Aborted then begin
  214. if UseSetupMessagesForErrors then
  215. raise Exception.Create(SetupMessages[msgErrorDownloadAborted])
  216. else
  217. Abort;
  218. end else if (FResult.HTTPStatusCode < 200) or (FResult.HTTPStatusCode > 299) then begin
  219. if UseSetupMessagesForErrors then
  220. raise Exception.Create(FmtSetupMessage(msgErrorDownloadFailed, [IntToStr(FResult.HTTPStatusCode), FResult.HTTPStatusText]))
  221. else
  222. raise Exception.Create(Format('%d %s', [FResult.HTTPStatusCode, FResult.HTTPStatusText]));
  223. end;
  224. end;
  225. end;
  226. function DownloadThreadFunc(Parameter: Pointer): Integer;
  227. begin
  228. const D = THTTPDataReceiver(Parameter);
  229. try
  230. D.DoDownload;
  231. except
  232. const Ex = AcquireExceptionObject;
  233. MemoryBarrier;
  234. D.FResult.SavedFatalException := Ex;
  235. end;
  236. MemoryBarrier;
  237. Result := 0;
  238. end;
  239. function THTTPDataReceiver.Download(const UseSetupMessagesForErrors: Boolean): Int64;
  240. begin
  241. var ThreadID: TThreadID;
  242. const ThreadHandle = BeginThread(nil, 0, DownloadThreadFunc, Self, 0, ThreadID);
  243. if ThreadHandle = 0 then
  244. raise Exception.Create('Failed to create download thread: ' + SysErrorMessage(GetLastError));
  245. try
  246. try
  247. while True do begin
  248. case WaitForSingleObject(ThreadHandle, 50) of
  249. WAIT_OBJECT_0: Break;
  250. WAIT_TIMEOUT: HandleProgress;
  251. WAIT_FAILED: raise Exception.Create('WaitForSingleObject failed: ' + SysErrorMessage(GetLastError));
  252. else
  253. raise Exception.Create('WaitForSingleObject returned unknown value');
  254. end;
  255. end;
  256. except
  257. { If an exception was raised during the loop (most likely it would
  258. be from the user's OnDownloadProgress handler), request abort
  259. and make one more attempt to wait on the thread. }
  260. FAbort := True; { Atomic so no lock }
  261. WaitForSingleObject(ThreadHandle, INFINITE);
  262. raise;
  263. end;
  264. finally
  265. CloseHandle(ThreadHandle);
  266. end;
  267. HandleProgress;
  268. HandleResult(UseSetupMessagesForErrors);
  269. Result := FResult.FileSize;
  270. end;
  271. function MaskPasswordInUrl(const Url: String): String;
  272. var
  273. Uri: TUri;
  274. begin
  275. Uri := TUri.Create(Url);
  276. if Uri.Password <> '' then begin
  277. Uri.Password := '***';
  278. Result := Uri.ToString;
  279. end else
  280. Result := URL;
  281. end;
  282. var
  283. DownloadTemporaryFileUser, DownloadTemporaryFilePass: String;
  284. procedure SetDownloadTemporaryFileCredentials(const User, Pass: String);
  285. begin
  286. DownloadTemporaryFileUser := User;
  287. DownloadTemporaryFilePass := Pass;
  288. end;
  289. function GetISSigUrl(const Url, ISSigUrl: String): String;
  290. begin
  291. if ISSigUrl <> '' then
  292. Result := ISSigUrl
  293. else begin
  294. const Uri = TUri.Create(Url); { This is a record so no need to free }
  295. Uri.Path := Uri.Path + ISSigExt;
  296. Result := Uri.ToString;
  297. end;
  298. end;
  299. function DownloadFile(const Url, CustomUserName, CustomPassword: String;
  300. const DestF: TFile; [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
  301. const OnSimpleDownloadProgress: TOnSimpleDownloadProgress;
  302. const OnSimpleDownloadProgressParam: Int64;
  303. const OnSimpleDownloadNoProgress: TOnSimpleDownloadNoProgress): Int64;
  304. var
  305. HTTPDataReceiver: THTTPDataReceiver;
  306. begin
  307. if Url = '' then
  308. InternalError('DownloadFile: Invalid Url value');
  309. LogFmt('Downloading file from %s', [MaskPasswordInURL(Url)]);
  310. HTTPDataReceiver := THTTPDataReceiver.Create(Url, CustomUserName, CustomPassword, DestF);
  311. try
  312. HTTPDataReceiver.OnSimpleDownloadProgress := OnSimpleDownloadProgress;
  313. HTTPDataReceiver.OnSimpleDownloadProgressParam := OnSimpleDownloadProgressParam;
  314. HTTPDataReceiver.OnSimpleDownloadNoProgress := OnSimpleDownloadNoProgress;
  315. { Download to specified handle }
  316. Result := HTTPDataReceiver.Download(False);
  317. { Check verification if specified, otherwise check everything else we can check }
  318. if Verification.Typ <> fvNone then begin
  319. var ExpectedFileHash: TSHA256Digest;
  320. if Verification.Typ = fvHash then
  321. ExpectedFileHash := Verification.Hash
  322. else
  323. DoISSigVerify(DestF, nil, ISSigSourceFilename, False, Verification.ISSigAllowedKeys, ExpectedFileHash);
  324. const FileHash = GetSHA256OfFile(DestF);
  325. if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
  326. VerificationError(veFileHashIncorrect);
  327. Log(VerificationSuccessfulLogMessage);
  328. end else begin
  329. if HTTPDataReceiver.ProgressMax > 0 then begin
  330. if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
  331. raise Exception.Create(FmtSetupMessage(msgErrorProgress, [IntToStr(HTTPDataReceiver.Progress), IntToStr(HTTPDataReceiver.ProgressMax)]))
  332. else if HTTPDataReceiver.ProgressMax <> Result then
  333. raise Exception.Create(FmtSetupMessage(msgErrorFileSize, [IntToStr(HTTPDataReceiver.ProgressMax), IntToStr(Result)]));
  334. end;
  335. end;
  336. finally
  337. HTTPDataReceiver.Free;
  338. end;
  339. end;
  340. function DownloadTemporaryFile(const Url, BaseName: String;
  341. [ref] const Verification: TSetupFileVerification; const OnDownloadProgress: TOnDownloadProgress;
  342. const OnDownloadNoProgress: TOnDownloadNoProgress; out DestFile: String): Int64;
  343. var
  344. TempFile: String;
  345. TempF: TFile;
  346. TempFileLeftOver: Boolean;
  347. HTTPDataReceiver: THTTPDataReceiver;
  348. begin
  349. if Url = '' then
  350. InternalError('DownloadTemporaryFile: Invalid Url value');
  351. if BaseName = '' then
  352. InternalError('DownloadTemporaryFile: Invalid BaseName value');
  353. DestFile := AddBackslash(TempInstallDir) + BaseName;
  354. LogFmt('Downloading temporary file from %s: %s', [MaskPasswordInURL(Url), DestFile]);
  355. { Does not disable FS redirection, like everything else working on the temp dir }
  356. { Prepare directory }
  357. var DidJustDeleteDestFile := False;
  358. if NewFileExists(DestFile) then begin
  359. if Verification.Typ = fvHash then begin
  360. if SHA256DigestsEqual(GetSHA256OfFile(DestFile), Verification.Hash) then begin
  361. Log(' File already downloaded.');
  362. Result := 0;
  363. Exit;
  364. end;
  365. end else if Verification.Typ = fvISSig then begin
  366. var ExistingFileName: String;
  367. var ExistingFileSize: Int64;
  368. var ExistingFileHash: TSHA256Digest;
  369. if ISSigVerifySignature(DestFile, GetISSigAllowedKeys(ISSigAvailableKeys, Verification.ISSigAllowedKeys),
  370. ExistingFileName, ExistingFileSize, ExistingFileHash, nil, nil, nil) then begin
  371. const DestF = TFile.Create(DestFile, fdOpenExisting, faRead, fsReadWrite);
  372. try
  373. { Not checking ExistingFileName because we can't be sure what the original filename was }
  374. if (DestF.Size = ExistingFileSize) and
  375. (SHA256DigestsEqual(GetSHA256OfFile(DestF), ExistingFileHash)) then begin
  376. Log(' File already downloaded.');
  377. Result := 0;
  378. Exit;
  379. end;
  380. finally
  381. DestF.Free;
  382. end;
  383. end;
  384. end;
  385. SetFileAttributes(PChar(DestFile), GetFileAttributes(PChar(DestFile)) and not FILE_ATTRIBUTE_READONLY);
  386. DelayDeleteFile(DestFile, 13, 50, 250);
  387. DidJustDeleteDestFile := True;
  388. end else
  389. ForceDirectories(PathExtractPath(DestFile));
  390. { Create temporary file }
  391. TempFile := GenerateUniqueName(PathExtractPath(DestFile), '.tmp');
  392. TempF := TFile.Create(TempFile, fdCreateAlways, faWrite, fsNone);
  393. TempFileLeftOver := True;
  394. HTTPDataReceiver := THTTPDataReceiver.Create(Url,
  395. DownloadTemporaryFileUser, DownloadTemporaryFilePass, TempF);
  396. try
  397. HTTPDataReceiver.BaseName := BaseName;
  398. HTTPDataReceiver.OnDownloadProgress := OnDownloadProgress;
  399. HTTPDataReceiver.OnDownloadNoProgress := OnDownloadNoProgress;
  400. { To test redirects: https://jrsoftware.org/download.php/is.exe
  401. To test expired certificates: https://expired.badssl.com/
  402. To test self-signed certificates: https://self-signed.badssl.com/
  403. To test basic authentication: https://guest:[email protected]/HTTP/Basic/
  404. To test 100 MB file: https://speed.hetzner.de/100MB.bin
  405. To test 1 GB file: https://speed.hetzner.de/1GB.bin
  406. To test file without a content length: https://github.com/jrsoftware/issrc/archive/main.zip }
  407. { Download to temporary file}
  408. Result := HTTPDataReceiver.Download(True);
  409. { Check verification if specified, otherwise check everything else we can check }
  410. if Verification.Typ <> fvNone then begin
  411. var ExpectedFileHash: TSHA256Digest;
  412. if Verification.Typ = fvHash then
  413. ExpectedFileHash := Verification.Hash
  414. else
  415. DoISSigVerify(TempF, nil, DestFile, False, Verification.ISSigAllowedKeys, ExpectedFileHash);
  416. FreeAndNil(TempF);
  417. const FileHash = GetSHA256OfFile(TempFile);
  418. if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
  419. VerificationError(veFileHashIncorrect);
  420. Log(VerificationSuccessfulLogMessage);
  421. end else begin
  422. FreeAndNil(TempF);
  423. if HTTPDataReceiver.ProgressMax > 0 then begin
  424. if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
  425. raise Exception.Create(FmtSetupMessage(msgErrorProgress, [IntToStr(HTTPDataReceiver.Progress), IntToStr(HTTPDataReceiver.ProgressMax)]))
  426. else if HTTPDataReceiver.ProgressMax <> Result then
  427. raise Exception.Create(FmtSetupMessage(msgErrorFileSize, [IntToStr(HTTPDataReceiver.ProgressMax), IntToStr(Result)]));
  428. end;
  429. end;
  430. { Rename the temporary file to the new name now, with retries if needed }
  431. const CapturableDestFile = DestFile;
  432. PerformFileOperationWithRetries(4, DidJustDeleteDestFile,
  433. function(out LastError: Cardinal): Boolean
  434. begin
  435. Result := MoveFile(PChar(TempFile), PChar(CapturableDestFile));
  436. if not Result then
  437. LastError := GetLastError;
  438. end,
  439. procedure(const LastError: Cardinal)
  440. begin
  441. LogFmt(' The existing file appears to be in use (%d). ' +
  442. 'Retrying.', [LastError]);
  443. Sleep(1000);
  444. end,
  445. procedure(const LastError: Cardinal; var TryOnceMore: Boolean)
  446. begin
  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.