Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ILSpy.AddIn.VS2022/source.extension.vsixmanifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion ILSpy.AddIn/source.extension.vsixmanifest.template
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions ILSpy.Tests/ILSpy.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<Compile Include="Analyzers\TypeUsedByAnalyzerTests.cs" />
<Compile Include="CommandLineArgumentsTests.cs" />
<Compile Include="ResourceReaderWriterTests.cs" />
<Compile Include="UpdateServiceTests.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
109 changes: 109 additions & 0 deletions ILSpy.Tests/UpdateServiceTests.cs
Original file line number Diff line number Diff line change
@@ -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)
});
}
}
}
36 changes: 31 additions & 5 deletions ILSpy/Updates/UpdateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,52 @@ 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";

public static AvailableVersionInfo LatestAvailableVersion { get; private set; }

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;
Expand Down
2 changes: 1 addition & 1 deletion doc/ILSpyAboutPage.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading