mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
#48: Prevent base URL from containing a file path, regardless of whether it's running on the file system or from a web server. Add tests and lint.
This commit is contained in:
@@ -42,14 +42,6 @@ namespace Squirrel
|
||||
Contract.Requires(filename.Contains(Path.DirectorySeparatorChar) == false);
|
||||
Contract.Requires(filesize > 0);
|
||||
|
||||
if(baseUrl != null)
|
||||
{
|
||||
if (Uri.IsWellFormedUriString(baseUrl, UriKind.Absolute) && !baseUrl.EndsWith("/"))
|
||||
baseUrl += "/";
|
||||
else if (!baseUrl.EndsWith(Path.DirectorySeparatorChar.ToString()))
|
||||
baseUrl += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
SHA1 = sha1; BaseUrl = baseUrl; Filename = filename; Filesize = filesize; IsDelta = isDelta;
|
||||
}
|
||||
|
||||
@@ -100,18 +92,14 @@ namespace Squirrel
|
||||
|
||||
string filename = m.Groups[2].Value;
|
||||
|
||||
// Extract the filename if a path or a uri is provided
|
||||
// Split the base URL and the filename if an URI is provided, throws if a path is provided
|
||||
string baseUrl = null;
|
||||
if(Uri.IsWellFormedUriString(filename, UriKind.Absolute))
|
||||
{
|
||||
var indexOfLastPathSeparator = filename.LastIndexOf("/");
|
||||
if(Utility.IsHttpUrl(filename)) {
|
||||
var indexOfLastPathSeparator = filename.LastIndexOf("/") + 1;
|
||||
baseUrl = filename.Substring(0, indexOfLastPathSeparator);
|
||||
filename = filename.Substring(indexOfLastPathSeparator + 1);
|
||||
}
|
||||
else if (filename.IndexOf(Path.DirectorySeparatorChar) > -1)
|
||||
{
|
||||
baseUrl = Path.GetDirectoryName(filename);
|
||||
filename = Path.GetFileName(filename);
|
||||
filename = filename.Substring(indexOfLastPathSeparator);
|
||||
} else if (filename.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) {
|
||||
throw new Exception(string.Format("Filename is not a valid URI and contains invalid characters: '{0}'", filename));
|
||||
}
|
||||
|
||||
long size = Int64.Parse(m.Groups[3].Value);
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace Squirrel
|
||||
|
||||
public static Version ToVersion(this string fileName)
|
||||
{
|
||||
var parts = (new FileInfo(fileName)).Name
|
||||
.Replace(".nupkg", "").Replace("-delta", "")
|
||||
var parts = Path.GetFileNameWithoutExtension(fileName)
|
||||
.Replace("-delta", "")
|
||||
.Split('.', '-').Reverse();
|
||||
|
||||
var numberRegex = new Regex(@"^\d+$");
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace Squirrel
|
||||
// Fetch the remote RELEASES file, whether it's a local dir or an
|
||||
// HTTP URL
|
||||
if (Utility.IsHttpUrl(updateUrlOrPath)) {
|
||||
if (updateUrlOrPath.EndsWith("/"))
|
||||
updateUrlOrPath = updateUrlOrPath.Substring(0, updateUrlOrPath.Length - 1);
|
||||
|
||||
this.Log().Info("Downloading RELEASES file from {0}", updateUrlOrPath);
|
||||
|
||||
try {
|
||||
|
||||
@@ -29,29 +29,46 @@ namespace Squirrel
|
||||
int toIncrement = (int)(100.0 / releasesToDownload.Count());
|
||||
|
||||
if (Utility.IsHttpUrl(updateUrlOrPath)) {
|
||||
// From Internet
|
||||
await releasesToDownload.ForEachAsync(async x => {
|
||||
var targetFile = Path.Combine(packagesDirectory, x.Filename);
|
||||
File.Delete(targetFile);
|
||||
if(!updateUrlOrPath.EndsWith("/"))
|
||||
updateUrlOrPath += '/';
|
||||
var sourceFileUrl = new Uri(new Uri(updateUrlOrPath), x.Filename).AbsoluteUri;
|
||||
await urlDownloader.DownloadFile(
|
||||
sourceFileUrl,
|
||||
targetFile);
|
||||
await downloadRelease(updateUrlOrPath, x, urlDownloader, targetFile);
|
||||
lock (progress) progress(current += toIncrement);
|
||||
});
|
||||
} else {
|
||||
await releasesToDownload.ForEachAsync(x => {
|
||||
// From Disk
|
||||
await releasesToDownload.ForEachAsync(async x => {
|
||||
var targetFile = Path.Combine(packagesDirectory, x.Filename);
|
||||
File.Copy(
|
||||
Path.Combine(updateUrlOrPath, x.Filename),
|
||||
targetFile,
|
||||
true);
|
||||
if (x.BaseUrl != null)
|
||||
await downloadRelease(updateUrlOrPath, x, urlDownloader, targetFile);
|
||||
else
|
||||
File.Copy(
|
||||
Path.Combine(updateUrlOrPath, x.Filename),
|
||||
targetFile,
|
||||
true);
|
||||
lock (progress) progress(current += toIncrement);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool isReleaseExplicitlyHttp(ReleaseEntry x)
|
||||
{
|
||||
return x.BaseUrl != null && Uri.IsWellFormedUriString(x.BaseUrl, UriKind.Absolute);
|
||||
}
|
||||
|
||||
Task downloadRelease(string updateBaseUrl, ReleaseEntry releaseEntry, IFileDownloader urlDownloader, string targetFile)
|
||||
{
|
||||
if (!updateBaseUrl.EndsWith("/"))
|
||||
updateBaseUrl += '/';
|
||||
|
||||
var sourceFileUrl = new Uri(new Uri(updateBaseUrl), releaseEntry.BaseUrl + releaseEntry.Filename).AbsoluteUri;
|
||||
|
||||
File.Delete(targetFile);
|
||||
return urlDownloader.DownloadFile(
|
||||
sourceFileUrl,
|
||||
targetFile);
|
||||
}
|
||||
|
||||
Task checksumAllPackages(IEnumerable<ReleaseEntry> releasesDownloaded)
|
||||
{
|
||||
return releasesDownloaded.ForEachAsync(x => checksumPackage(x));
|
||||
|
||||
@@ -331,12 +331,11 @@ namespace Squirrel
|
||||
|
||||
public static bool IsHttpUrl(string urlOrPath)
|
||||
{
|
||||
try {
|
||||
var url = new Uri(urlOrPath);
|
||||
return new[] {"https", "http"}.Contains(url.Scheme.ToLowerInvariant());
|
||||
} catch (Exception) {
|
||||
Uri uri;
|
||||
if (!Uri.TryCreate(urlOrPath, UriKind.Absolute, out uri))
|
||||
return false;
|
||||
}
|
||||
|
||||
return uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps;
|
||||
}
|
||||
|
||||
public static async Task DeleteDirectoryWithFallbackToNextReboot(string dir)
|
||||
|
||||
@@ -209,6 +209,14 @@ namespace Squirrel.Update
|
||||
|
||||
public void Releasify(string package, string targetDir = null, string packagesDir = null, string bootstrapperExe = null, string backgroundGif = null, string signingOpts = null, string baseUrl = null)
|
||||
{
|
||||
if (baseUrl != null) {
|
||||
if (!Utility.IsHttpUrl(baseUrl))
|
||||
throw new Exception(string.Format("Invalid --baseUrl '{0}'. A base URL must start with http or https and be a valid URI.", baseUrl));
|
||||
|
||||
if (!baseUrl.EndsWith("/"))
|
||||
baseUrl += "/";
|
||||
}
|
||||
|
||||
targetDir = targetDir ?? ".\\Releases";
|
||||
packagesDir = packagesDir ?? ".";
|
||||
bootstrapperExe = bootstrapperExe ?? ".\\Setup.exe";
|
||||
|
||||
@@ -10,11 +10,11 @@ namespace Squirrel.Tests.Core
|
||||
public class ReleaseEntryTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec MyCoolApp-1.0.nupkg 1004502", "MyCoolApp-1.0.nupkg", 1004502, null)]
|
||||
[InlineData(@"3a2eadd15dd984e4559f2b4d790ec8badaeb6a39 MyCoolApp-1.1.nupkg 1040561", "MyCoolApp-1.1.nupkg", 1040561, null)]
|
||||
[InlineData(@"94689fede03fed7ab59c24337673a27837f0c3ec MyCoolApp-1.0.nupkg 1004502", "MyCoolApp-1.0.nupkg", 1004502, null)]
|
||||
[InlineData(@"3a2eadd15dd984e4559f2b4d790ec8badaeb6a39 MyCoolApp-1.1.nupkg 1040561", "MyCoolApp-1.1.nupkg", 1040561, null)]
|
||||
[InlineData(@"14db31d2647c6d2284882a2e101924a9c409ee67 MyCoolApp-1.1.nupkg.delta 80396", "MyCoolApp-1.1.nupkg.delta", 80396, null)]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://test.org/Folder/MyCoolApp-1.2.nupkg 1231953", "MyCoolApp-1.2.nupkg", 1231953, "http://test.org/Folder/")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 \\Somewhere\NetworkShare\MyCoolApp-1.3.nupkg.delta 0", @"MyCoolApp-1.3.nupkg.delta", 0, @"\\Somewhere\NetworkShare\")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://test.org/Folder/MyCoolApp-1.2.nupkg 2569", "MyCoolApp-1.2.nupkg", 2569, "http://test.org/Folder/")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 https://www.test.org/Folder/MyCoolApp-1.2-delta.nupkg 1231953", "MyCoolApp-1.2-delta.nupkg", 1231953, "https://www.test.org/Folder/")]
|
||||
public void ParseValidReleaseEntryLines(string releaseEntry, string fileName, long fileSize, string baseUrl)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
@@ -23,6 +23,26 @@ namespace Squirrel.Tests.Core
|
||||
Assert.Equal(baseUrl, fixture.BaseUrl);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 file:/C/Folder/MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 C:\Folder\MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 ..\OtherFolder\MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 ../OtherFolder/MyCoolApp-0.0.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 \\Somewhere\NetworkShare\MyCoolApp-0.0.nupkg.delta 0")]
|
||||
public void ParseThrowsWhenInvalidReleaseEntryLines(string releaseEntry)
|
||||
{
|
||||
Assert.Throws<Exception>(() => ReleaseEntry.ParseReleaseEntry(releaseEntry));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 file.nupkg 0")]
|
||||
[InlineData(@"0000000000000000000000000000000000000000 http://path/file.nupkg 0")]
|
||||
public void EntryAsStringMatchesParsedInput(string releaseEntry)
|
||||
{
|
||||
var fixture = ReleaseEntry.ParseReleaseEntry(releaseEntry);
|
||||
Assert.Equal(releaseEntry, fixture.EntryAsString);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Squirrel.Core.1.0.0.0.nupkg", 4457, "75255cfd229a1ed1447abe1104f5635e69975d30")]
|
||||
[InlineData("Squirrel.Core.1.1.0.0.nupkg", 15830, "9baf1dbacb09940086c8c62d9a9dbe69fe1f7593")]
|
||||
|
||||
Reference in New Issue
Block a user