| | | 1 | | namespace 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> |
| | | 10 | | public 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:<chemin>" en dev avec hot-reload, "embedded:<ressource>" |
| | | 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 | | { |
| | 152 | 46 | | if (AllowedRoot is null) return true; |
| | 8 | 47 | | var rootFull = Path.GetFullPath(AllowedRoot).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar |
| | 8 | 48 | | var pathFull = Path.GetFullPath(fullPath); |
| | 8 | 49 | | return pathFull.StartsWith(rootFull + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) |
| | 8 | 50 | | || 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 | | { |
| | 84 | 59 | | lock (_writableLock) _writablePaths.Add(fullPath); |
| | 42 | 60 | | } |
| | | 61 | | |
| | | 62 | | public bool IsWritable(string fullPath) |
| | | 63 | | { |
| | 64 | 64 | | lock (_writableLock) return _writablePaths.Contains(fullPath); |
| | 32 | 65 | | } |
| | | 66 | | |
| | | 67 | | public void Log(string level, string msg) |
| | | 68 | | { |
| | 206 | 69 | | var line = $"{DateTime.UtcNow:O} {BuildId} {level,-5} {msg}"; |
| | 412 | 70 | | lock (_logLock) File.AppendAllText(LogFile, line + Environment.NewLine); |
| | 206 | 71 | | } |
| | | 72 | | } |