using System; using System.IO; using System.Runtime.InteropServices; using Gott.Models; namespace Gott.Services; public static class WorkspaceHelper { /// True wenn die Plattform Windows ist. internal static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); /// Plattformunabhängiges Temp-Verzeichnis ohne Trailing-Separator. internal static string TempDir => Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar); /// /// Prüft ob ein Pfad ein Sub-Pfad eines Basis-Pfads ist (oder gleich). /// Auf Windows case-insensitive, auf Linux case-sensitive. /// internal static bool IsSubPathOf(string path, string basePath) { var comparison = IsWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; var normalizedPath = Path.GetFullPath(path); var normalizedBase = Path.GetFullPath(basePath); return normalizedPath.Equals(normalizedBase, comparison) || normalizedPath.StartsWith(normalizedBase + Path.DirectorySeparatorChar, comparison); } /// /// Gibt den relativen Pfad zurück – mit nativen Separatoren (Backslash auf Windows, / auf Linux). /// internal static string GetRelativePath(string relativeTo, string path) { return Path.GetRelativePath(relativeTo, path); } /// /// Gibt den Shell-Dateinamen zurück (cmd.exe auf Windows, /bin/bash auf Linux). /// internal static string ShellExecutable => IsWindows ? "cmd.exe" : "/bin/bash"; /// /// Löst den Workspace-Pfad einer Session auf. /// session.Workspace ist relativ zum user.DefaultPath. /// Gibt den absoluten Pfad zurück oder null wenn kein Workspace konfiguriert ist. /// public static string ResolveWorkspace(SessionRecord session, UserRecord user) { if (session == null || string.IsNullOrWhiteSpace(session.Workspace)) return null; var workspace = session.Workspace.Trim(); // Wenn der Workspace bereits ein absoluter Pfad ist (Legacy/Fallback) if (Path.IsPathRooted(workspace)) return Path.GetFullPath(workspace); // Relativer Pfad → mit DefaultPath des Users kombinieren var basePath = user?.DefaultPath?.Trim(); if (string.IsNullOrWhiteSpace(basePath)) return null; // Kein DefaultPath gesetzt → relativer Workspace kann nicht aufgelöst werden var resolved = Path.GetFullPath(Path.Combine(basePath, workspace)); // Sicherheit: darf nicht aus dem DefaultPath ausbrechen if (!IsSubPathOf(resolved, Path.GetFullPath(basePath))) return null; return resolved; } /// /// Validiert einen relativen Workspace-Pfad. /// Prüft auf Path-Traversal (.. etc.). /// Gibt den bereinigten Pfad zurück oder null bei ungültigem Pfad. /// public static string ValidateRelativeWorkspace(string workspace) { if (string.IsNullOrWhiteSpace(workspace)) return null; var trimmed = workspace.Trim().TrimStart('/', '\\').TrimEnd('/', '\\'); if (string.IsNullOrEmpty(trimmed)) return null; // Path-Traversal verhindern if (trimmed.Contains("..") || trimmed.Contains("~")) return null; // Keine absoluten Pfade erlauben if (Path.IsPathRooted(trimmed)) return null; return trimmed; } /// /// Konvertiert einen absoluten Workspace-Pfad in einen relativen Pfad zum DefaultPath. /// Wird beim Speichern verwendet, damit alte absolute Pfade in relative umgewandelt werden. /// public static string ToRelativeWorkspace(string absoluteWorkspace, string defaultPath) { if (string.IsNullOrWhiteSpace(absoluteWorkspace)) return null; if (string.IsNullOrWhiteSpace(defaultPath)) return absoluteWorkspace; // Kein DefaultPath → absoluten Pfad beibehalten (Legacy) var normalizedBase = Path.GetFullPath(defaultPath.Trim()); var normalizedWorkspace = Path.GetFullPath(absoluteWorkspace.Trim()); var comparison = IsWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; if (normalizedWorkspace.StartsWith(normalizedBase + Path.DirectorySeparatorChar, comparison)) return normalizedWorkspace.Substring(normalizedBase.Length + 1); if (normalizedWorkspace.Equals(normalizedBase, comparison)) return ""; return absoluteWorkspace; // Außerhalb des DefaultPath → absoluten Pfad beibehalten (Legacy) } }