Move file escape to utility and add test for filename web-uri downloading

This commit is contained in:
Caelan Sayler
2024-06-11 09:14:50 +01:00
parent c94e2840bb
commit ff27bdf874
3 changed files with 64 additions and 24 deletions

View File

@@ -264,6 +264,28 @@ namespace Velopack
})); }));
} }
/// <summary>
/// Escapes file name such that the file name is safe for writing to disk in the packages folder
/// </summary>
public static string GetSafeFilename(string fileName)
{
string safeFileName = Path.GetFileName(fileName);
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
if (safeFileName.IndexOfAny(invalidFileNameChars) != -1) {
StringBuilder safeName = new();
foreach (char ch in safeFileName) {
if (Array.IndexOf(invalidFileNameChars, ch) == -1)
safeName.Append(ch);
else
safeName.Append('_');
}
safeFileName = safeName.ToString();
}
return safeFileName;
}
public static string GetDefaultTempBaseDirectory() public static string GetDefaultTempBaseDirectory()
{ {
string tempDir; string tempDir;

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -232,7 +231,7 @@ namespace Velopack
var appTempDir = Locator.AppTempDir!; var appTempDir = Locator.AppTempDir!;
var appPackageDir = Locator.PackagesDir!; var appPackageDir = Locator.PackagesDir!;
var completeFile = Path.Combine(appPackageDir, GetSafeFilename(targetRelease.FileName)); var completeFile = Path.Combine(appPackageDir, Utility.GetSafeFilename(targetRelease.FileName));
var incompleteFile = completeFile + ".partial"; var incompleteFile = completeFile + ".partial";
try { try {
@@ -261,7 +260,7 @@ namespace Velopack
$"Only full update will be available."); $"Only full update will be available.");
} else { } else {
using var _1 = Utility.GetTempDirectory(out var deltaStagingDir, appTempDir); using var _1 = Utility.GetTempDirectory(out var deltaStagingDir, appTempDir);
string basePackagePath = Path.Combine(appPackageDir, GetSafeFilename(updates.BaseRelease.FileName)); string basePackagePath = Path.Combine(appPackageDir, Utility.GetSafeFilename(updates.BaseRelease.FileName));
if (!File.Exists(basePackagePath)) if (!File.Exists(basePackagePath))
throw new Exception($"Unable to find base package {basePackagePath} for delta update."); throw new Exception($"Unable to find base package {basePackagePath} for delta update.");
EasyZip.ExtractZipToDirectory(Log, basePackagePath, deltaStagingDir); EasyZip.ExtractZipToDirectory(Log, basePackagePath, deltaStagingDir);
@@ -319,27 +318,6 @@ namespace Velopack
CleanPackagesExcept(completeFile); CleanPackagesExcept(completeFile);
} }
// Ensures that the file name is safe for writing to disk without escaping the packages folder
static string GetSafeFilename(string fileName)
{
string safeFileName = Path.GetFileName(fileName);
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
if (safeFileName.IndexOfAny(invalidFileNameChars) != -1 ) {
StringBuilder safeName = new();
foreach(char ch in safeFileName) {
if (Array.IndexOf(invalidFileNameChars, ch) == -1)
safeName.Append(ch);
else
safeName.Append('_');
}
safeFileName = safeName.ToString();
}
return safeFileName;
}
} }
/// <summary> /// <summary>

View File

@@ -100,6 +100,46 @@ public class UpdateManagerTests
return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) }; return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) };
} }
[Fact]
public void CanDownloadFilesAsUrl()
{
var fixture = PathHelper.GetFixture("AvaloniaCrossPlat-1.0.11-win-full.nupkg");
using var logger = _output.BuildLoggerFor<UpdateManagerTests>();
using var _1 = Utility.GetTempDirectory(out var tempPath);
var dl = new FakeDownloader() {
MockedResponseBytes = Encoding.UTF8.GetBytes(SimpleJson.SerializeObject(
new VelopackAssetFeed {
Assets = new VelopackAsset[] {
new VelopackAsset() {
PackageId = "AvaloniaCrossPlat",
Version = new SemanticVersion(1, 0, 11),
Type = VelopackAssetType.Full,
FileName = $"https://mysite.com/releases/AvaloniaCrossPlat$-1.1.0.nupkg",
SHA1 = Utility.CalculateFileSHA1(fixture),
Size = new FileInfo(fixture).Length,
} }
}))
};
var source = new SimpleWebSource("http://any.com", dl);
var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger);
var um = new UpdateManager(source, null, logger, locator);
var info = um.CheckForUpdates();
Assert.NotNull(info);
Assert.True(new SemanticVersion(1, 0, 11) == info.TargetFullRelease.Version);
Assert.Equal(0, info.DeltasToTarget.Count());
Assert.False(info.IsDowngrade);
Assert.StartsWith($"http://any.com/releases.{VelopackRuntimeInfo.SystemOs.GetOsShortName()}.json?", dl.LastUrl);
dl.MockedResponseBytes = File.ReadAllBytes(fixture);
dl.WriteMockLocalFile = true;
um.DownloadUpdates(info);
Assert.True(File.Exists(Path.Combine(tempPath, "AvaloniaCrossPlat$-1.1.0.nupkg")));
Assert.Equal(Path.Combine(tempPath, "AvaloniaCrossPlat$-1.1.0.nupkg.partial"), dl.LastLocalFile);
Assert.Equal("https://mysite.com/releases/AvaloniaCrossPlat$-1.1.0.nupkg", dl.LastUrl);
}
[Fact] [Fact]
public void CheckFromLocal() public void CheckFromLocal()
{ {