Skip to content

Commit 4179075

Browse files
authored
Merge pull request #488 from GeneralLibrary/fix/ssl-policy-and-httpclient-lifecycle
fix: SSL policy not covering file downloads + HttpClient lifecycle issues
2 parents 655c60d + 1e12fd0 commit 4179075

3 files changed

Lines changed: 68 additions & 10 deletions

File tree

src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,11 @@ private async Task<GeneralUpdateBootstrap> LaunchWithStrategy(IStrategy roleStra
153153

154154
// ── Network-level extensions (global, applied before any HTTP call) ──
155155
var sslPolicy = ResolveExtension<Security.ISslValidationPolicy>();
156-
if (sslPolicy != null) Network.VersionService.SetSslValidationPolicy(sslPolicy);
156+
if (sslPolicy != null)
157+
{
158+
Network.VersionService.SetSslValidationPolicy(sslPolicy);
159+
Network.HttpClientProvider.SetSslValidationPolicy(sslPolicy);
160+
}
157161
var authProvider = ResolveExtension<Security.IHttpAuthProvider>();
158162
if (authProvider != null) Network.VersionService.SetDefaultAuthProvider(authProvider);
159163

@@ -403,7 +407,11 @@ private async Task<GeneralUpdateBootstrap> LaunchSilentAsync()
403407

404408
// Network-level extensions (global, applied before any HTTP call)
405409
var sslPolicy = ResolveExtension<Security.ISslValidationPolicy>();
406-
if (sslPolicy != null) Network.VersionService.SetSslValidationPolicy(sslPolicy);
410+
if (sslPolicy != null)
411+
{
412+
Network.VersionService.SetSslValidationPolicy(sslPolicy);
413+
Network.HttpClientProvider.SetSslValidationPolicy(sslPolicy);
414+
}
407415
var authProvider = ResolveExtension<Security.IHttpAuthProvider>();
408416
if (authProvider != null) Network.VersionService.SetDefaultAuthProvider(authProvider);
409417

src/c#/GeneralUpdate.Core/Network/HttpClientProvider.cs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,65 @@
1+
using System;
12
using System.Net.Http;
3+
using System.Net.Security;
4+
using System.Security.Cryptography.X509Certificates;
5+
using GeneralUpdate.Core.Security;
26

37
namespace GeneralUpdate.Core.Network;
48

59
/// <summary>
6-
/// Provides a shared static <see cref="HttpClient"/> instance.
10+
/// Provides a shared static <see cref="HttpClient"/> instance with configurable SSL validation.
711
/// Reusing a single HttpClient prevents socket exhaustion.
812
/// Do NOT dispose clients obtained from here.
913
/// </summary>
14+
/// <remarks>
15+
/// <para>
16+
/// SSL certificate validation behaviour is controlled by the
17+
/// <see cref="SetSslValidationPolicy"/> method. The default policy is
18+
/// <see cref="StrictSslValidationPolicy"/> (rejects any certificate with SSL errors).
19+
/// </para>
20+
/// <para>
21+
/// This class mirrors the SSL validation pattern used by <see cref="VersionService"/>,
22+
/// ensuring that both API calls and file downloads respect the same global SSL policy.
23+
/// </para>
24+
/// </remarks>
1025
public static class HttpClientProvider
1126
{
12-
private static readonly HttpClient _shared = new();
27+
private static ISslValidationPolicy _sslPolicy = new StrictSslValidationPolicy();
28+
private static readonly HttpClient _shared;
29+
30+
static HttpClientProvider()
31+
{
32+
var handler = new HttpClientHandler();
33+
handler.ServerCertificateCustomValidationCallback = (req, cert, chain, errors) =>
34+
_sslPolicy.ValidateCertificate(cert, chain, errors);
35+
_shared = new HttpClient(handler, disposeHandler: false)
36+
{
37+
// Let per-request CancellationTokenSource.CancelAfter (set by
38+
// HttpDownloadExecutor & VersionService) control timeout — the
39+
// global HttpClient.Timeout (default ~100s) would otherwise cap
40+
// requests before a caller-configured longer DownloadTimeout.
41+
Timeout = System.Threading.Timeout.InfiniteTimeSpan
42+
};
43+
}
44+
45+
/// <summary>
46+
/// Sets the global SSL certificate validation policy for all download requests.
47+
/// </summary>
48+
/// <param name="policy">The SSL validation policy instance. Must not be null.</param>
49+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="policy"/> is null.</exception>
50+
/// <remarks>
51+
/// <para>
52+
/// This policy affects all HTTPS download requests made through
53+
/// <see cref="HttpDownloadExecutor"/> and other components that use the shared
54+
/// <see cref="HttpClient"/>. The default is <see cref="StrictSslValidationPolicy"/>.
55+
/// </para>
56+
/// <para>
57+
/// The validation callback delegates to the stored policy instance, so the policy
58+
/// can be changed at any time — new requests will use the updated policy.
59+
/// </para>
60+
/// </remarks>
61+
public static void SetSslValidationPolicy(ISslValidationPolicy policy)
62+
=> _sslPolicy = policy ?? throw new ArgumentNullException(nameof(policy));
1363

1464
/// <summary>
1565
/// Gets the shared <see cref="HttpClient"/> instance. Do NOT dispose.

src/c#/GeneralUpdate.Core/Strategy/OssStrategy.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Diagnostics;
44
using System.IO;
55
using System.Linq;
6-
using System.Net.Http;
76
using System.Text;
87
using System.Text.Json;
98
using System.Threading.Tasks;
@@ -462,8 +461,7 @@ private static async Task DownloadVersionConfig(string url, string path)
462461
File.SetAttributes(path, FileAttributes.Normal);
463462
File.Delete(path);
464463
}
465-
using var httpClient = GeneralUpdate.Core.Network.HttpClientProvider.Shared;
466-
var bytes = await httpClient.GetByteArrayAsync(url).ConfigureAwait(false);
464+
var bytes = await Network.HttpClientProvider.Shared.GetByteArrayAsync(url).ConfigureAwait(false);
467465
File.WriteAllBytes(path, bytes);
468466
}
469467

@@ -506,11 +504,13 @@ private async Task DownloadAssetsAsync(List<DownloadAsset> assets, string target
506504
}
507505
else
508506
{
509-
using var httpClient = new HttpClient
507+
var options = new DownloadOrchestratorOptions
510508
{
511-
Timeout = TimeSpan.FromSeconds(_configInfo?.DownloadTimeOut > 0 ? _configInfo!.DownloadTimeOut : DefaultTimeOut)
509+
DownloadTimeout = TimeSpan.FromSeconds(
510+
_configInfo?.DownloadTimeOut > 0 ? _configInfo!.DownloadTimeOut : DefaultTimeOut)
512511
};
513-
var orchestrator = new DefaultDownloadOrchestrator(httpClient);
512+
var orchestrator = new DefaultDownloadOrchestrator(
513+
Network.HttpClientProvider.Shared, options);
514514
await orchestrator.ExecuteAsync(plan, targetPath).ConfigureAwait(false);
515515
}
516516
}

0 commit comments

Comments
 (0)