Forge
csharpdeeb25a2
1#nullable enable
2
3using CascadeIDE.Features.Workspace.DataAcquisition;
4
5namespace CascadeIDE.Features.MagicLink;
6
7/// <summary>Проверка, что root — допустимый workspace для Magic Link (ADR 0157 §2).</summary>
8public static class CideMagicLinkWorkspaceGuard
9{
10 public static bool TryValidateRoot(string? workspaceRoot, out string normalizedRoot, out string? error)
11 {
12 normalizedRoot = "";
13 error = null;
14
15 if (string.IsNullOrWhiteSpace(workspaceRoot))
16 {
17 error = "root не задан.";
18 return false;
19 }
20
21 try
22 {
23 normalizedRoot = Path.GetFullPath(workspaceRoot.Trim());
24 }
25 catch (Exception ex)
26 {
27 error = ex.Message;
28 return false;
29 }
30
31 if (!Directory.Exists(normalizedRoot))
32 {
33 error = "root не существует.";
34 return false;
35 }
36
37 if (RepositoryWorkspaceTomlLoader.TryLoad(normalizedRoot) is not null)
38 return true;
39
40 var sln = Directory.EnumerateFiles(normalizedRoot, "*.sln", SearchOption.TopDirectoryOnly).FirstOrDefault()
41 ?? Directory.EnumerateFiles(normalizedRoot, "*.slnx", SearchOption.TopDirectoryOnly).FirstOrDefault();
42 if (sln is not null)
43 return true;
44
45 error = "root не похож на workspace (нет .cascade/workspace.toml и *.sln).";
46 return false;
47 }
48
49 public static bool TryResolveUnderRoot(string workspaceRoot, string repoRelativePath, out string absolutePath, out string? error)
50 {
51 absolutePath = "";
52 error = null;
53
54 var rel = repoRelativePath.Replace('\\', '/').Trim();
55 if (rel.Contains("..", StringComparison.Ordinal))
56 {
57 error = "path traversal запрещён.";
58 return false;
59 }
60
61 try
62 {
63 absolutePath = Path.GetFullPath(Path.Combine(workspaceRoot, rel.Replace('/', Path.DirectorySeparatorChar)));
64 }
65 catch (Exception ex)
66 {
67 error = ex.Message;
68 return false;
69 }
70
71 if (!absolutePath.StartsWith(workspaceRoot.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)
72 && !string.Equals(absolutePath, workspaceRoot, StringComparison.OrdinalIgnoreCase))
73 {
74 error = "путь вне workspace root.";
75 return false;
76 }
77
78 return true;
79 }
80}
81
View only · write via MCP/CIDE