Secure library paths
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace Jellyfin.Controller.Tests.IO;
|
||||
|
||||
public class FileSystemHelperTests
|
||||
{
|
||||
private static readonly string _parentPath = Path.Combine(Path.GetTempPath(), "jellyfin-test", "root", "default");
|
||||
|
||||
[Theory]
|
||||
[InlineData("Movies")]
|
||||
[InlineData("My Movies")]
|
||||
[InlineData("..2")]
|
||||
[InlineData("...")]
|
||||
[InlineData("a.b")]
|
||||
public void GetChildPath_ValidName_ReturnsPathInsideParent(string name)
|
||||
{
|
||||
var path = FileSystemHelper.GetChildPath(_parentPath, name);
|
||||
|
||||
Assert.Equal(Path.Combine(_parentPath, name), path);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(".")]
|
||||
[InlineData("..")]
|
||||
[InlineData("../..")]
|
||||
[InlineData("../../etc")]
|
||||
[InlineData("Movies/../..")]
|
||||
[InlineData("/var/lib/jellyfin/data")]
|
||||
[InlineData("sub/folder")]
|
||||
[InlineData("with\0null")]
|
||||
public void GetChildPath_EscapingName_ReturnsNull(string name)
|
||||
{
|
||||
Assert.Null(FileSystemHelper.GetChildPath(_parentPath, name));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("..\\..")]
|
||||
[InlineData("sub\\folder")]
|
||||
[InlineData("C:\\Windows")]
|
||||
public void GetChildPath_WindowsSeparator_DoesNotEscapeParent(string name)
|
||||
{
|
||||
var path = FileSystemHelper.GetChildPath(_parentPath, name);
|
||||
|
||||
// On Windows these are rejected outright, on other platforms a backslash is a legal file name character.
|
||||
Assert.True(path is null || string.Equals(Path.GetDirectoryName(path), _parentPath, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChildPath_ParentWithTrailingSeparator_ReturnsPathInsideParent()
|
||||
{
|
||||
var path = FileSystemHelper.GetChildPath(_parentPath + Path.DirectorySeparatorChar, "Movies");
|
||||
|
||||
Assert.Equal(Path.Combine(_parentPath, "Movies"), path);
|
||||
}
|
||||
}
|
||||
+52
@@ -114,6 +114,58 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Priority(1)]
|
||||
[InlineData("..")]
|
||||
[InlineData("../..")]
|
||||
[InlineData(".")]
|
||||
[InlineData("test/../..")]
|
||||
[InlineData("/var/lib/jellyfin/data")]
|
||||
public async Task DeleteLibrary_PathTraversal_NotFound(string name)
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
|
||||
|
||||
using var response = await client.DeleteAsync($"Library/VirtualFolders?name={Uri.EscapeDataString(name)}", TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Priority(1)]
|
||||
[InlineData("..")]
|
||||
[InlineData("../..")]
|
||||
[InlineData(".")]
|
||||
[InlineData("test/../..")]
|
||||
[InlineData("/var/lib/jellyfin/data")]
|
||||
public async Task RenameLibrary_PathTraversalNewName_BadRequest(string newName)
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
|
||||
|
||||
using var response = await client.PostAsync(
|
||||
$"Library/VirtualFolders/Name?name=test&newName={Uri.EscapeDataString(newName)}",
|
||||
null,
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Priority(1)]
|
||||
[InlineData("..")]
|
||||
[InlineData("../..")]
|
||||
[InlineData("/var/lib/jellyfin/data")]
|
||||
public async Task RenameLibrary_PathTraversalName_NotFound(string name)
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
|
||||
|
||||
using var response = await client.PostAsync(
|
||||
$"Library/VirtualFolders/Name?name={Uri.EscapeDataString(name)}&newName=renamed",
|
||||
null,
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Priority(1)]
|
||||
public async Task DeleteLibrary_Valid_Success()
|
||||
|
||||
Reference in New Issue
Block a user