< Summary - Jellyfin

Information
Class: Jellyfin.Extensions.PathHelper
Assembly: Jellyfin.Extensions
File(s): /srv/git/jellyfin/src/Jellyfin.Extensions/PathHelper.cs
Line coverage
100%
Covered lines: 16
Uncovered lines: 0
Coverable lines: 16
Total lines: 77
Line coverage: 100%
Branch coverage
83%
Covered branches: 10
Total branches: 12
Branch coverage: 83.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 7/22/2026 - 12:16:22 AM Line coverage: 100% (16/16) Branch coverage: 83.3% (10/12) Total lines: 77 7/22/2026 - 12:16:22 AM Line coverage: 100% (16/16) Branch coverage: 83.3% (10/12) Total lines: 77

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetSafeLeafFileName(...)87.5%88100%
IsContainedIn(...)75%44100%

File(s)

/srv/git/jellyfin/src/Jellyfin.Extensions/PathHelper.cs

#LineLine coverage
 1using System;
 2using System.IO;
 3
 4namespace Jellyfin.Extensions;
 5
 6/// <summary>
 7/// Helpers for safely composing filesystem paths from untrusted input.
 8/// </summary>
 9/// <remarks>
 10/// <see cref="Path.Combine(string, string)"/> has two issues that matter in
 11/// any code that joins a trusted directory with an externally-supplied name:
 12/// it neither normalises <c>..</c> nor rejects a rooted second argument
 13/// (a rooted second arg silently discards the first). Use the helpers below
 14/// any time the name comes from media metadata, request input, archive
 15/// entries, or any other channel that can be influenced by a third party.
 16/// </remarks>
 17public static class PathHelper
 18{
 19    /// <summary>
 20    /// Reduces a possibly-untrusted file name to a safe leaf-only name with no
 21    /// directory components.
 22    /// </summary>
 23    /// <param name="fileName">The candidate file name.</param>
 24    /// <returns>
 25    /// The leaf component of <paramref name="fileName"/>, or <c>null</c> if
 26    /// the input has no usable leaf (empty, <c>.</c>, or <c>..</c>).
 27    /// </returns>
 28    public static string? GetSafeLeafFileName(string? fileName)
 29    {
 1530        if (string.IsNullOrEmpty(fileName))
 31        {
 232            return null;
 33        }
 34
 1335        var leaf = Path.GetFileName(fileName);
 1336        if (string.IsNullOrEmpty(leaf) || string.Equals(leaf, ".", StringComparison.Ordinal) || string.Equals(leaf, ".."
 37        {
 238            return null;
 39        }
 40
 1141        return leaf;
 42    }
 43
 44    /// <summary>
 45    /// Returns whether <paramref name="candidate"/> resolves to a path that
 46    /// equals or is contained inside <paramref name="root"/>.
 47    /// </summary>
 48    /// <param name="root">The directory the candidate must remain inside.</param>
 49    /// <param name="candidate">The candidate absolute or relative path.</param>
 50    /// <returns><c>true</c> if the candidate is inside or equal to root; otherwise <c>false</c>.</returns>
 51    /// <remarks>
 52    /// Both arguments are resolved via <see cref="Path.GetFullPath(string)"/>
 53    /// so <c>..</c> segments are collapsed before the comparison. The root is
 54    /// compared with a trailing directory separator to prevent prefix
 55    /// collisions (e.g. <c>/var/data</c> must not be accepted as a parent of
 56    /// <c>/var/dataset</c>).
 57    /// </remarks>
 58    public static bool IsContainedIn(string root, string candidate)
 59    {
 860        ArgumentException.ThrowIfNullOrEmpty(root);
 861        ArgumentException.ThrowIfNullOrEmpty(candidate);
 62
 863        var fullRoot = Path.GetFullPath(root);
 864        var fullCandidate = Path.GetFullPath(candidate);
 65
 866        if (string.Equals(fullCandidate, fullRoot, StringComparison.Ordinal))
 67        {
 168            return true;
 69        }
 70
 771        var rootWithSep = fullRoot.EndsWith(Path.DirectorySeparatorChar)
 772            ? fullRoot
 773            : fullRoot + Path.DirectorySeparatorChar;
 74
 775        return fullCandidate.StartsWith(rootWithSep, StringComparison.Ordinal);
 76    }
 77}