#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:
Christian Rondeau
2014-09-28 13:51:39 -04:00
parent 1c98657bdb
commit 25696a2368
7 changed files with 76 additions and 41 deletions

View File

@@ -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);

View File

@@ -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+$");

View File

@@ -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 {

View File

@@ -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));

View File

@@ -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)

View File

@@ -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";

View File

@@ -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")]