Skip to content
18 changes: 2 additions & 16 deletions src/BackupChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,8 @@ private static IEnumerable<BackupMetadata> NextLogBackup(IEnumerable<BackupMetad

private static bool IsValidFilePath(BackupMetadata meta)
{
if(BackupFileTools.IsUrl(meta.PhysicalDeviceName))
return true;

// A quick check before leaning on exceptions
if(Path.GetInvalidPathChars().Any(meta.PhysicalDeviceName.Contains))
return false;

try {
// This will throw an argument exception if the path is invalid
Path.GetFullPath(meta.PhysicalDeviceName);
// A relative path won't help us much if the destination is another server. It needs to be rooted.
return Path.IsPathRooted(meta.PhysicalDeviceName);
}
catch(Exception) {
return false;
}
var path = meta.PhysicalDeviceName;
return BackupFileTools.IsValidFileUrl(path) || BackupFileTools.IsValidFilePath(path);
Comment thread
AdityaNPL marked this conversation as resolved.
}
}
}
26 changes: 24 additions & 2 deletions src/SmoFacade/BackupFileTools.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace AgDatabaseMove.SmoFacade
{
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;


Expand All @@ -13,9 +15,11 @@ public enum BackupType
Log // trn / L
}

public static bool IsUrl(string path)
public static bool IsValidFileUrl(string path)
{
return Regex.IsMatch(path, @"(http(|s):\/)(\/[^\s]+)+\.([a-zA-Z]+)$");
return Uri.TryCreate(path, UriKind.Absolute, out var uriResult)
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps || uriResult.IsUnc)
&& Path.HasExtension(path);
}

public static string BackupTypeToExtension(BackupType type)
Expand Down Expand Up @@ -45,5 +49,23 @@ public static BackupType BackupTypeAbbrevToType(string type)
throw new ArgumentException("Invalid backup type");
}
}

public static bool IsValidFilePath(string path)
{
// A quick check before leaning on exceptions
if(Path.GetInvalidPathChars().Any(path.Contains)) {
return false;
}

try {
// This will throw an argument exception if the path is invalid
Path.GetFullPath(path);
// A relative path won't help us much if the destination is another server. It needs to be rooted.
return Path.IsPathRooted(path) && Path.HasExtension(path);
}
catch(Exception) {
return false;
}
}
}
}
4 changes: 2 additions & 2 deletions src/SmoFacade/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public void Restore(IEnumerable<BackupMetadata> backupOrder, string databaseName
var restore = new Restore { Database = databaseName, NoRecovery = true };

foreach(var backup in backupOrder) {
var device = BackupFileTools.IsUrl(backup.PhysicalDeviceName) ? DeviceType.Url : DeviceType.File;
var device = BackupFileTools.IsValidFileUrl(backup.PhysicalDeviceName) ? DeviceType.Url : DeviceType.File;
var backupDeviceItem = new BackupDeviceItem(backup.PhysicalDeviceName, device);
if(_credentialName != null && device == DeviceType.Url)
backupDeviceItem.CredentialName = _credentialName;
Expand Down Expand Up @@ -207,7 +207,7 @@ private void Backup(Backup backup, string backupDirectoryPathQuery, string datab
var backupDirectory = BackupDirectoryOrDefault(backupDirectoryPathQuery);
var filePath =
$"{backupDirectory}/{databaseName}_backup_{DateTime.Now.ToString("yyyy_MM_dd_hhmmss_fff")}.{BackupFileTools.BackupTypeToExtension(type)}";
var deviceType = BackupFileTools.IsUrl(filePath) ? DeviceType.Url : DeviceType.File;
var deviceType = BackupFileTools.IsValidFileUrl(filePath) ? DeviceType.Url : DeviceType.File;

var bdi = new BackupDeviceItem(filePath, deviceType);
if(_credentialName != null && deviceType == DeviceType.Url)
Expand Down
78 changes: 53 additions & 25 deletions tests/AgDatabaseMove.Unit/FileToolsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,39 @@ namespace AgDatabaseMove.Unit

public class BackupFileToolsTest
{
public static IEnumerable<object[]> UrlFileExamples => new List<object[]> {
new object[] { "https://hello/a.bak" },
new object[] { "https://hello/a.full" },
new object[] { "https://storage-account.blob.core.windows.net/container/file.trn" },
new object[] { "https://hello/a.diff" },
new object[] { "https://a.diff" },
new object[] { "https://1/2/3/4/5/a.diff" },
new object[] { "https://storage-account.blob.core.windows.net/container/file.bad" },
new object[]
{ "https://storage-account.blob.core.windows.net/container/sql/db_name/backup_2020_09_02_170003_697.trn" },
new object[] { "http://hello/a.bak" }
};

public static IEnumerable<object[]> NonUrlFileExamples => new List<object[]> {
new object[] { @"c:\hello\a.bak" },
new object[] { @"\\abc\hello/a.bak" },
new object[] { "https://storage-account.blob.core.windows.net/container" },
new object[] { "http://storage-account.blob.core.windows.net/container" }
Comment thread
AdityaNPL marked this conversation as resolved.
};

[Theory]
[MemberData(nameof(UrlFileExamples))]
public void UrlFilesAreUrl(string file)
[InlineData(@"https://hello/a.bak")]
[InlineData(@"https://hello/a.full")]
[InlineData(@"https://storage-account.blob.core.windows.net/container/file.trn")]
[InlineData(@"https://hello/a.diff")]
[InlineData(@"https://1/2/3/4/5/a.diff")]
[InlineData(@"https://storage-account.blob.core.windows.net/container/file.bad")]
[InlineData(@"https://storage-account.blob.core.windows.net/container/sql/db_name/backup_2020_09_02_170003_697.trn")]
[InlineData(@"http://a.bak")]
[InlineData(@"\\UNC\syntax\path\file.ext")]
[InlineData(@"\\server\file.ext")]
[InlineData(@"//Unix/syntax/file.ext")]
public void ValidUrlTests(string url)
{
Assert.True(BackupFileTools.IsUrl(file));
Assert.True(BackupFileTools.IsValidFileUrl(url));
}

[Theory]
[MemberData(nameof(NonUrlFileExamples))]
public void NonUrlFilesAreNotUrl(string file)
[InlineData(@"")]
[InlineData(@" ")]
[InlineData(@"c:\hello\a.bak")]
[InlineData(@"\\C:/")]
[InlineData(@"\wrongUNC\file.txt")]
[InlineData(@"/wrongUNC/file.txt")]
[InlineData(@"\\server\dir")]
[InlineData(@"\\server\dir\")]
[InlineData(@"https://storage-account.blob.core.windows.net/dir")]
[InlineData(@"http://storage-account.blob.core.windows.net/dir")]
[InlineData(@"http://storage-account.blob.core.windows.net/dir/")]
public void InvalidUrlTests(string url)
{
Assert.False(BackupFileTools.IsUrl(file));
Assert.False(BackupFileTools.IsValidFileUrl(url));
}

[Theory]
Expand All @@ -58,5 +59,32 @@ public void BackupTypeAbbrevToType(string abbrev, BackupFileTools.BackupType typ
{
Assert.Equal(type, BackupFileTools.BackupTypeAbbrevToType(abbrev));
}

[Theory]
[InlineData(@"C:\dir\file.ext")]
[InlineData(@"/some/file.ext")]

public void ValidPathTests(string path)
{
Assert.True(BackupFileTools.IsValidFilePath(path));
}

[Theory]
[InlineData(@"")]
[InlineData(@" ")]
[InlineData(@"/dir")]
[InlineData(@"/")]
[InlineData(@"file.ext")]
[InlineData(@"dir\file.ext")]
[InlineData(@"C dir\file.ext")]
[InlineData(@"dir")]
[InlineData(@"C:\dir\")]
[InlineData(@"C:\dir")]
[InlineData(@"C:\")]
[InlineData(@"C:\inval|d")]
public void InValidPathTests(string path)
{
Assert.False(BackupFileTools.IsValidFilePath(path));
}
}
}