mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Use releaseTag with fallback to downloadUrl in updates.xml (#3713)
* Use releaseTag with fallback to downloadUrl in updates.xml * Add tests * Prevent arbitrary downloadUrl - must start with BaseUrl as well * Remove custom domain ilspy.net in end-user visible places
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<Identity Id="ebf12ca7-a1fd-4aee-a894-4a0c5682fc2f" Version="$INSERTVERSION$" Language="en-US" Publisher="SharpDevelop Team" />
|
||||
<DisplayName>ILSpy 2022</DisplayName>
|
||||
<Description xml:space="preserve">Integrates the ILSpy decompiler into Visual Studio.</Description>
|
||||
<MoreInfo>https://ilspy.net</MoreInfo>
|
||||
<MoreInfo>https://github.com/icsharpcode/ILSpy/</MoreInfo>
|
||||
<License>LICENSE</License>
|
||||
<Icon>ILSpy-Large.ico</Icon>
|
||||
<Tags>ILSpy;IL;decompile;decompiler;decompilation;C#;CSharp;.NET;Productivity;Open Source;Free</Tags>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Identity Id="a9120dbe-164a-4891-842f-fb7829273838" Version="$INSERTVERSION$" Language="en-US" Publisher="ic#code" />
|
||||
<DisplayName>ILSpy</DisplayName>
|
||||
<Description xml:space="preserve">Integrates the ILSpy decompiler into Visual Studio.</Description>
|
||||
<MoreInfo>https://ilspy.net</MoreInfo>
|
||||
<MoreInfo>https://github.com/icsharpcode/ILSpy/</MoreInfo>
|
||||
<License>LICENSE</License>
|
||||
<Icon>ILSpy-Large.ico</Icon>
|
||||
</Metadata>
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<Compile Include="Analyzers\TypeUsedByAnalyzerTests.cs" />
|
||||
<Compile Include="CommandLineArgumentsTests.cs" />
|
||||
<Compile Include="ResourceReaderWriterTests.cs" />
|
||||
<Compile Include="UpdateServiceTests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using AwesomeAssertions;
|
||||
|
||||
using ICSharpCode.ILSpy.Updates;
|
||||
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace ICSharpCode.ILSpy.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class UpdateServiceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetLatestVersionAsync_UsesReleaseTag_WhenReleaseTagIsPresent()
|
||||
{
|
||||
const string xml = """
|
||||
<updateInfo>
|
||||
<band id="stable">
|
||||
<latestVersion>10.0.0.0</latestVersion>
|
||||
<releaseTag>v10.0</releaseTag>
|
||||
<downloadUrl>https://example.com/ignored.zip</downloadUrl>
|
||||
</band>
|
||||
</updateInfo>
|
||||
""";
|
||||
|
||||
using var client = new HttpClient(new StubHttpMessageHandler(xml));
|
||||
|
||||
var result = await UpdateService.GetLatestVersionAsync(client, new Uri("https://example.com/updates.xml"));
|
||||
|
||||
result.Version.Should().Be(new Version(10, 0, 0, 0));
|
||||
result.DownloadUrl.Should().Be("https://github.com/icsharpcode/ILSpy/releases/tag/v10.0");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetLatestVersionAsync_ReturnsNullDownloadUrl_WhenReleaseTagContainsPathTraversalAttempt()
|
||||
{
|
||||
const string xml = """
|
||||
<updateInfo>
|
||||
<band id="stable">
|
||||
<latestVersion>10.0.0.0</latestVersion>
|
||||
<releaseTag>../malicious</releaseTag>
|
||||
<downloadUrl>https://example.com/ignored.zip</downloadUrl>
|
||||
</band>
|
||||
</updateInfo>
|
||||
""";
|
||||
|
||||
using var client = new HttpClient(new StubHttpMessageHandler(xml));
|
||||
|
||||
var result = await UpdateService.GetLatestVersionAsync(client, new Uri("https://example.com/updates.xml"));
|
||||
|
||||
result.Version.Should().Be(new Version(10, 0, 0, 0));
|
||||
result.DownloadUrl.Should().BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetLatestVersionAsync_UsesDownloadUrl_WhenReleaseTagIsMissing()
|
||||
{
|
||||
const string xml = """
|
||||
<updateInfo>
|
||||
<band id="stable">
|
||||
<latestVersion>10.0.0.0</latestVersion>
|
||||
<downloadUrl>https://github.com/icsharpcode/ILSpy/releases/tag/v10.0</downloadUrl>
|
||||
</band>
|
||||
</updateInfo>
|
||||
""";
|
||||
|
||||
using var client = new HttpClient(new StubHttpMessageHandler(xml));
|
||||
|
||||
var result = await UpdateService.GetLatestVersionAsync(client, new Uri("https://example.com/updates.xml"));
|
||||
|
||||
result.Version.Should().Be(new Version(10, 0, 0, 0));
|
||||
result.DownloadUrl.Should().Be("https://github.com/icsharpcode/ILSpy/releases/tag/v10.0");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetLatestVersionAsync_UsesDownloadUrl_ButFailsBecauseBaseUrlDoesntMatch()
|
||||
{
|
||||
const string xml = """
|
||||
<updateInfo>
|
||||
<band id="stable">
|
||||
<latestVersion>10.0.0.0</latestVersion>
|
||||
<downloadUrl>https://example.com/ilspy.zip</downloadUrl>
|
||||
</band>
|
||||
</updateInfo>
|
||||
""";
|
||||
|
||||
using var client = new HttpClient(new StubHttpMessageHandler(xml));
|
||||
|
||||
var result = await UpdateService.GetLatestVersionAsync(client, new Uri("https://example.com/updates.xml"));
|
||||
|
||||
result.Version.Should().Be(new Version(10, 0, 0, 0));
|
||||
result.DownloadUrl.Should().BeNull();
|
||||
}
|
||||
|
||||
sealed class StubHttpMessageHandler(string responseContent) : HttpMessageHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) {
|
||||
Content = new StringContent(responseContent)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ namespace ICSharpCode.ILSpy.Updates
|
||||
{
|
||||
internal static class UpdateService
|
||||
{
|
||||
const string ReleaseTagBaseUrl = "https://github.com/icsharpcode/ILSpy/releases/tag/";
|
||||
static readonly Uri UpdateUrl = new Uri("https://icsharpcode.github.io/ILSpy/updates.xml");
|
||||
const string band = "stable";
|
||||
|
||||
@@ -35,19 +36,44 @@ namespace ICSharpCode.ILSpy.Updates
|
||||
|
||||
public static async Task<AvailableVersionInfo> GetLatestVersionAsync()
|
||||
{
|
||||
var client = new HttpClient(new HttpClientHandler() {
|
||||
using var client = new HttpClient(new HttpClientHandler() {
|
||||
UseProxy = true,
|
||||
UseDefaultCredentials = true
|
||||
});
|
||||
string data = await GetWithRedirectsAsync(client, UpdateUrl).ConfigureAwait(false);
|
||||
|
||||
return await GetLatestVersionAsync(client, UpdateUrl).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<AvailableVersionInfo> GetLatestVersionAsync(HttpClient client, Uri updateUrl)
|
||||
{
|
||||
// Issue #3707: Remove 301 redirect logic once ilspy.net CNAME gone
|
||||
string data = await GetWithRedirectsAsync(client, updateUrl).ConfigureAwait(false);
|
||||
|
||||
XDocument doc = XDocument.Load(new StringReader(data));
|
||||
var bands = doc.Root.Elements("band").ToList();
|
||||
var currentBand = bands.FirstOrDefault(b => (string)b.Attribute("id") == band) ?? bands.First();
|
||||
Version version = new Version((string)currentBand.Element("latestVersion"));
|
||||
string url = (string)currentBand.Element("downloadUrl");
|
||||
if (!(url.StartsWith("http://", StringComparison.Ordinal) || url.StartsWith("https://", StringComparison.Ordinal)))
|
||||
url = null; // don't accept non-urls
|
||||
|
||||
string url = null;
|
||||
string releaseTag = (string)currentBand.Element("releaseTag");
|
||||
|
||||
if (releaseTag != null)
|
||||
{
|
||||
url = ReleaseTagBaseUrl + releaseTag;
|
||||
|
||||
// Prevent path traversal: normalize the URI and verify it still starts with the expected base
|
||||
if (!new Uri(url).AbsoluteUri.StartsWith(ReleaseTagBaseUrl, StringComparison.Ordinal))
|
||||
url = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Issue #3707: Remove else branch fallback logic once releaseTag version has shipped + 6 months
|
||||
url = (string)currentBand.Element("downloadUrl");
|
||||
|
||||
// Prevent arbitrary URLs: verify it starts with the expected base
|
||||
if (!new Uri(url).AbsoluteUri.StartsWith(ReleaseTagBaseUrl, StringComparison.Ordinal))
|
||||
url = null;
|
||||
}
|
||||
|
||||
LatestAvailableVersion = new AvailableVersionInfo { Version = version, DownloadUrl = url };
|
||||
return LatestAvailableVersion;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ILSpy is the open-source .NET assembly browser and decompiler.
|
||||
|
||||
Website: https://ilspy.net/
|
||||
Website: https://github.com/icsharpcode/ILSpy/
|
||||
Found a bug? https://github.com/icsharpcode/ILSpy/issues/new/choose
|
||||
|
||||
Copyright 2011-2026 AlphaSierraPapa for the ILSpy team
|
||||
|
||||
Reference in New Issue
Block a user