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)
}
}