< Summary

Information
Class: AspxLint.Server.ServerSession
Assembly: AspxLint.Server
File(s): D:\a\claude-aspx-lint\claude-aspx-lint\src\AspxLint.Server\ServerSession.cs
Line coverage
100%
Covered lines: 12
Uncovered lines: 0
Coverable lines: 12
Total lines: 72
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsUnderAllowedRoot(...)100%44100%
AddWritable(...)100%11100%
IsWritable(...)100%11100%
Log(...)100%11100%

File(s)

D:\a\claude-aspx-lint\claude-aspx-lint\src\AspxLint.Server\ServerSession.cs

#LineLine coverage
 1namespace AspxLint.Server;
 2
 3/// <summary>
 4/// Etat partage du serveur pour la session courante : identite (build + token),
 5/// fichier de log, set des paths inscriptibles via /api/save, et delegate
 6/// pour charger la dashboard HTML (depuis disque en dev, depuis ressource
 7/// embarquee sinon).
 8/// Resolu par injection de dependances dans les handlers de route.
 9/// </summary>
 10public sealed class ServerSession
 11{
 12    public required string BuildId { get; init; }
 13    public required string Token { get; init; }
 14    public required string LogFile { get; init; }
 15
 16    /// <summary>
 17    /// Description de la source de la dashboard (pour les logs). Format :
 18    /// "disk:&lt;chemin&gt;" en dev avec hot-reload, "embedded:&lt;ressource&gt;"
 19    /// en .exe self-contained ou conteneur.
 20    /// </summary>
 21    public required string DashboardSource { get; init; }
 22
 23    /// <summary>
 24    /// Charge le HTML de la dashboard (rappelle a chaque requete /, donc
 25    /// supporte le hot-reload en dev sans cache).
 26    /// </summary>
 27    public required Func<Task<string>> LoadDashboardHtml { get; init; }
 28
 29    /// <summary>
 30    /// Racine canonique a laquelle scan / save / restore sont confines.
 31    /// Null = pas de scoping (mode developpement).
 32    /// </summary>
 33    public string? AllowedRoot { get; init; }
 34
 35    /// <summary>
 36    /// Si true, les endpoints d'ecriture (/api/save, /api/restore) renvoient 403.
 37    /// </summary>
 38    public bool ReadOnly { get; init; }
 39
 40    /// <summary>
 41    /// Verifie qu'un chemin (apres normalisation Path.GetFullPath) est sous
 42    /// AllowedRoot. Si AllowedRoot est null, retourne toujours true.
 43    /// </summary>
 44    public bool IsUnderAllowedRoot(string fullPath)
 45    {
 15246        if (AllowedRoot is null) return true;
 847        var rootFull = Path.GetFullPath(AllowedRoot).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
 848        var pathFull = Path.GetFullPath(fullPath);
 849        return pathFull.StartsWith(rootFull + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)
 850            || pathFull.Equals(rootFull, StringComparison.OrdinalIgnoreCase);
 51    }
 52
 53    private readonly object _logLock = new();
 54    private readonly object _writableLock = new();
 55    private readonly HashSet<string> _writablePaths = new(StringComparer.OrdinalIgnoreCase);
 56
 57    public void AddWritable(string fullPath)
 58    {
 8459        lock (_writableLock) _writablePaths.Add(fullPath);
 4260    }
 61
 62    public bool IsWritable(string fullPath)
 63    {
 6464        lock (_writableLock) return _writablePaths.Contains(fullPath);
 3265    }
 66
 67    public void Log(string level, string msg)
 68    {
 20669        var line = $"{DateTime.UtcNow:O} {BuildId} {level,-5} {msg}";
 41270        lock (_logLock) File.AppendAllText(LogFile, line + Environment.NewLine);
 20671    }
 72}