using System;
using System.IO;
namespace Jellyfin.Extensions;
///
/// Helpers for safely composing filesystem paths from untrusted input.
///
///
/// has two issues that matter in
/// any code that joins a trusted directory with an externally-supplied name:
/// it neither normalises .. nor rejects a rooted second argument
/// (a rooted second arg silently discards the first). Use the helpers below
/// any time the name comes from media metadata, request input, archive
/// entries, or any other channel that can be influenced by a third party.
///
public static class PathHelper
{
///
/// Reduces a possibly-untrusted file name to a safe leaf-only name with no
/// directory components.
///
/// The candidate file name.
///
/// The leaf component of , or null if
/// the input has no usable leaf (empty, ., or ..).
///
public static string? GetSafeLeafFileName(string? fileName)
{
if (string.IsNullOrEmpty(fileName))
{
return null;
}
var leaf = Path.GetFileName(fileName);
if (string.IsNullOrEmpty(leaf) || string.Equals(leaf, ".", StringComparison.Ordinal) || string.Equals(leaf, "..", StringComparison.Ordinal))
{
return null;
}
return leaf;
}
///
/// Returns whether resolves to a path that
/// equals or is contained inside .
///
/// The directory the candidate must remain inside.
/// The candidate absolute or relative path.
/// true if the candidate is inside or equal to root; otherwise false.
///
/// Both arguments are resolved via
/// so .. segments are collapsed before the comparison. The root is
/// compared with a trailing directory separator to prevent prefix
/// collisions (e.g. /var/data must not be accepted as a parent of
/// /var/dataset).
///
public static bool IsContainedIn(string root, string candidate)
{
ArgumentException.ThrowIfNullOrEmpty(root);
ArgumentException.ThrowIfNullOrEmpty(candidate);
var fullRoot = Path.GetFullPath(root);
var fullCandidate = Path.GetFullPath(candidate);
if (string.Equals(fullCandidate, fullRoot, StringComparison.Ordinal))
{
return true;
}
var rootWithSep = fullRoot.EndsWith(Path.DirectorySeparatorChar)
? fullRoot
: fullRoot + Path.DirectorySeparatorChar;
return fullCandidate.StartsWith(rootWithSep, StringComparison.Ordinal);
}
}