Fixed: Performance of symbolic link detection and infinite recursion
This commit is contained in:
parent
1509e737c2
commit
f56003e288
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Mono.Posix;
|
||||||
|
using Mono.Unix;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Mono.Disk;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Mono.Test.DiskProviderTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[Platform("Mono")]
|
||||||
|
public class SymbolicLinkResolverFixture : TestBase<SymbolicLinkResolver>
|
||||||
|
{
|
||||||
|
public SymbolicLinkResolverFixture()
|
||||||
|
{
|
||||||
|
MonoOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_follow_nested_symlinks()
|
||||||
|
{
|
||||||
|
var rootDir = GetTempFilePath();
|
||||||
|
var tempDir1 = Path.Combine(rootDir, "dir1");
|
||||||
|
var tempDir2 = Path.Combine(rootDir, "dir2");
|
||||||
|
var subDir1 = Path.Combine(tempDir1, "subdir1");
|
||||||
|
var file1 = Path.Combine(tempDir2, "file1");
|
||||||
|
var file2 = Path.Combine(tempDir2, "file2");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempDir1);
|
||||||
|
Directory.CreateDirectory(tempDir2);
|
||||||
|
File.WriteAllText(file2, "test");
|
||||||
|
|
||||||
|
new UnixSymbolicLinkInfo(subDir1).CreateSymbolicLinkTo("../dir2");
|
||||||
|
new UnixSymbolicLinkInfo(file1).CreateSymbolicLinkTo("file2");
|
||||||
|
|
||||||
|
var realPath = Subject.GetCompleteRealPath(Path.Combine(subDir1, "file1"));
|
||||||
|
|
||||||
|
realPath.Should().Be(file2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_throw_on_infinite_loop()
|
||||||
|
{
|
||||||
|
var rootDir = GetTempFilePath();
|
||||||
|
var tempDir1 = Path.Combine(rootDir, "dir1");
|
||||||
|
var subDir1 = Path.Combine(tempDir1, "subdir1");
|
||||||
|
var file1 = Path.Combine(tempDir1, "file1");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(tempDir1);
|
||||||
|
|
||||||
|
new UnixSymbolicLinkInfo(subDir1).CreateSymbolicLinkTo("../../dir1/subdir1/baddir");
|
||||||
|
|
||||||
|
var realPath = Subject.GetCompleteRealPath(file1);
|
||||||
|
|
||||||
|
realPath.Should().Be(file1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Mono.Unix;
|
using Mono.Unix;
|
||||||
using Mono.Unix.Native;
|
using Mono.Unix.Native;
|
||||||
|
@ -11,8 +13,6 @@ namespace NzbDrone.Mono.Disk
|
||||||
string GetCompleteRealPath(string path);
|
string GetCompleteRealPath(string path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mono's own implementation doesn't handle exceptions very well.
|
|
||||||
// All of this code was copied from mono with minor changes.
|
|
||||||
public class SymbolicLinkResolver : ISymbolicLinkResolver
|
public class SymbolicLinkResolver : ISymbolicLinkResolver
|
||||||
{
|
{
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -28,34 +28,27 @@ namespace NzbDrone.Mono.Disk
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string[] dirs;
|
var realPath = path;
|
||||||
int lastIndex;
|
for (var links = 0; links < 32; links++)
|
||||||
GetPathComponents(path, out dirs, out lastIndex);
|
{
|
||||||
|
var wasSymLink = TryFollowFirstSymbolicLink(ref realPath);
|
||||||
|
if (!wasSymLink)
|
||||||
|
{
|
||||||
|
return realPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var realPath = new StringBuilder();
|
var ex = new UnixIOException(Errno.ELOOP);
|
||||||
if (dirs.Length > 0)
|
_logger.Warn("Failed to check for symlinks in the path {0}: {1}", path, ex.Message);
|
||||||
{
|
return path;
|
||||||
var dir = UnixPath.IsPathRooted(path) ? "/" : "";
|
|
||||||
dir += dirs[0];
|
|
||||||
realPath.Append(GetRealPath(dir));
|
|
||||||
}
|
|
||||||
for (var i = 1; i < lastIndex; ++i)
|
|
||||||
{
|
|
||||||
realPath.Append("/").Append(dirs[i]);
|
|
||||||
var realSubPath = GetRealPath(realPath.ToString());
|
|
||||||
realPath.Remove(0, realPath.Length);
|
|
||||||
realPath.Append(realSubPath);
|
|
||||||
}
|
|
||||||
return realPath.ToString();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Debug(ex, string.Format("Failed to check for symlinks in the path {0}", path));
|
_logger.Debug(ex, "Failed to check for symlinks in the path {0}", path);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void GetPathComponents(string path, out string[] components, out int lastIndex)
|
private static void GetPathComponents(string path, out string[] components, out int lastIndex)
|
||||||
{
|
{
|
||||||
var dirs = path.Split(UnixPath.DirectorySeparatorChar);
|
var dirs = path.Split(UnixPath.DirectorySeparatorChar);
|
||||||
|
@ -87,10 +80,62 @@ namespace NzbDrone.Mono.Disk
|
||||||
lastIndex = target;
|
lastIndex = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetRealPath(string path)
|
private bool TryFollowFirstSymbolicLink(ref string path)
|
||||||
{
|
{
|
||||||
do
|
string[] dirs;
|
||||||
|
int lastIndex;
|
||||||
|
GetPathComponents(path, out dirs, out lastIndex);
|
||||||
|
|
||||||
|
if (lastIndex == 0)
|
||||||
{
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var realPath = "";
|
||||||
|
|
||||||
|
for (var i = 0; i < lastIndex; ++i)
|
||||||
|
{
|
||||||
|
if (i != 0 || UnixPath.IsPathRooted(path))
|
||||||
|
{
|
||||||
|
realPath = string.Concat(realPath, UnixPath.DirectorySeparatorChar, dirs[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
realPath = string.Concat(realPath, dirs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathValid = TryFollowSymbolicLink(ref realPath, out var wasSymLink);
|
||||||
|
|
||||||
|
if (!pathValid || wasSymLink)
|
||||||
|
{
|
||||||
|
// If the path does not exist, or it was a symlink then we need to concat the remaining dir components and start over (or return)
|
||||||
|
var count = lastIndex - i - 1;
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
realPath = string.Concat(realPath, UnixPath.DirectorySeparatorChar, string.Join(UnixPath.DirectorySeparatorChar.ToString(), dirs, i + 1, lastIndex - i - 1));
|
||||||
|
}
|
||||||
|
path = realPath;
|
||||||
|
return pathValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryFollowSymbolicLink(ref string path, out bool wasSymLink)
|
||||||
|
{
|
||||||
|
if (!UnixFileSystemInfo.TryGetFileSystemEntry(path, out var fsentry) || !fsentry.Exists)
|
||||||
|
{
|
||||||
|
wasSymLink = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fsentry.IsSymbolicLink)
|
||||||
|
{
|
||||||
|
wasSymLink = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var link = UnixPath.TryReadLink(path);
|
var link = UnixPath.TryReadLink(path);
|
||||||
|
|
||||||
if (link == null)
|
if (link == null)
|
||||||
|
@ -101,9 +146,11 @@ namespace NzbDrone.Mono.Disk
|
||||||
_logger.Trace("Checking path {0} for symlink returned error {1}, assuming it's not a symlink.", path, errno);
|
_logger.Trace("Checking path {0} for symlink returned error {1}, assuming it's not a symlink.", path, errno);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
wasSymLink = true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (UnixPath.IsPathRooted(link))
|
if (UnixPath.IsPathRooted(link))
|
||||||
{
|
{
|
||||||
path = link;
|
path = link;
|
||||||
|
@ -113,8 +160,10 @@ namespace NzbDrone.Mono.Disk
|
||||||
path = UnixPath.GetDirectoryName(path) + UnixPath.DirectorySeparatorChar + link;
|
path = UnixPath.GetDirectoryName(path) + UnixPath.DirectorySeparatorChar + link;
|
||||||
path = UnixPath.GetCanonicalPath(path);
|
path = UnixPath.GetCanonicalPath(path);
|
||||||
}
|
}
|
||||||
} while (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
wasSymLink = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue