fix(launcher): harden updater crash recovery
This commit is contained in:
parent
2d2a5b5046
commit
1955ca8ab5
27 changed files with 2714 additions and 544 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace AcDream.Launcher.Core.Updates;
|
||||
|
|
@ -55,15 +56,11 @@ public sealed class VerifiedArtifactDownloader
|
|||
bool ownsDestination = false;
|
||||
try
|
||||
{
|
||||
using HttpResponseMessage response = await _httpClient.GetAsync(
|
||||
using HttpResponseMessage response = await SendWithValidatedRedirectsAsync(
|
||||
artifact.Url,
|
||||
HttpCompletionOption.ResponseHeadersRead,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
ReleaseManifestClient.RequireSecureOrLoopback(
|
||||
response.RequestMessage?.RequestUri ?? artifact.Url,
|
||||
"artifact redirect");
|
||||
if (response.Content.Headers.ContentLength is long contentLength
|
||||
&& contentLength != artifact.Size)
|
||||
{
|
||||
|
|
@ -183,6 +180,86 @@ public sealed class VerifiedArtifactDownloader
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> SendWithValidatedRedirectsAsync(
|
||||
Uri initialUri,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
bool allowLoopbackHttp = initialUri.Scheme == Uri.UriSchemeHttp
|
||||
&& initialUri.IsLoopback;
|
||||
Uri current = initialUri;
|
||||
var visited = new HashSet<string>(StringComparer.Ordinal);
|
||||
for (int redirectCount = 0;;)
|
||||
{
|
||||
ReleaseManifestClient.RequireTransport(
|
||||
current,
|
||||
"artifact redirect",
|
||||
allowLoopbackHttp);
|
||||
if (!visited.Add(current.AbsoluteUri))
|
||||
{
|
||||
throw new LauncherUpdateException(
|
||||
"The release artifact redirect chain contains a loop.");
|
||||
}
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, current);
|
||||
HttpResponseMessage response = await _httpClient.SendAsync(
|
||||
request,
|
||||
HttpCompletionOption.ResponseHeadersRead,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
Uri effectiveUri = response.RequestMessage?.RequestUri ?? current;
|
||||
if (!Uri.Equals(effectiveUri, current))
|
||||
{
|
||||
response.Dispose();
|
||||
throw new LauncherUpdateException(
|
||||
"The artifact HTTP transport followed an automatic redirect; "
|
||||
+ "every redirect must be validated before it is requested.");
|
||||
}
|
||||
|
||||
if (!IsRedirect(response.StatusCode))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (redirectCount >= ReleaseManifestClient.MaximumRedirects)
|
||||
{
|
||||
throw new LauncherUpdateException(
|
||||
$"The release artifact exceeded "
|
||||
+ $"{ReleaseManifestClient.MaximumRedirects} redirects.");
|
||||
}
|
||||
|
||||
Uri? location = response.Headers.Location;
|
||||
if (location is null)
|
||||
{
|
||||
throw new LauncherUpdateException(
|
||||
"The release artifact redirect has no Location header.");
|
||||
}
|
||||
|
||||
Uri next = location.IsAbsoluteUri
|
||||
? location
|
||||
: new Uri(current, location);
|
||||
ReleaseManifestClient.RequireTransport(
|
||||
next,
|
||||
"artifact redirect",
|
||||
allowLoopbackHttp);
|
||||
current = next;
|
||||
redirectCount++;
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRedirect(HttpStatusCode statusCode) => statusCode is
|
||||
HttpStatusCode.MovedPermanently
|
||||
or HttpStatusCode.Found
|
||||
or HttpStatusCode.SeeOther
|
||||
or HttpStatusCode.TemporaryRedirect
|
||||
or HttpStatusCode.PermanentRedirect;
|
||||
|
||||
internal static void TryDelete(string path)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue