File size: 2,278 Bytes
3ce1a40 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | using Repoverse.Core.Generation;
namespace Repoverse.Core.Bridge;
public sealed record SourcePage(string Path, int Offset, string[] Lines, bool HasMore);
/// <summary>Read-only, bounded source pages for artifacts in one explicitly mapped local repository.</summary>
public sealed class LocalSourceReader(WorldManifest manifest, string repo, string sourceRoot)
{
private readonly string _root = Path.GetFullPath(sourceRoot);
private readonly HashSet<string> _allowed = manifest.Buildings.Where(b => b.Repo.Key == repo.ToLowerInvariant())
.SelectMany(b => b.Interior.Rooms).SelectMany(r => r.Artifacts).Select(a => a.Path).ToHashSet(StringComparer.Ordinal);
public SourcePage Read(string requestedRepo, string relative, int offset)
{
if (!requestedRepo.Equals(repo,StringComparison.OrdinalIgnoreCase) || !_allowed.Contains(relative))
throw new ArgumentException("Source is not an artifact in the mapped repository.");
if (offset is < 0 or > 100_000 || Path.IsPathRooted(relative)) throw new ArgumentException("Invalid source page.");
var parts = relative.Replace('\\','/').Split('/');
if (parts.Any(p => p is "" or "." or ".." || p.Contains(':'))) throw new ArgumentException("Invalid source path.");
// Reject links/junctions at every component, including the configured root.
var target = _root;
if ((File.GetAttributes(target) & FileAttributes.ReparsePoint) != 0) throw new ArgumentException("Linked source roots are unsupported.");
foreach (var part in parts) {
target = Path.Combine(target,part);
if ((File.GetAttributes(target) & FileAttributes.ReparsePoint) != 0) throw new ArgumentException("Linked source paths are unsupported.");
}
if (new FileInfo(target).Length > 2*1024*1024) throw new ArgumentException("Source exceeds the 2 MiB inspection limit.");
var text = File.ReadAllText(target);
if (text.Contains('\0')) throw new ArgumentException("Binary artifact; open it in its native application.");
var lines = text.Replace("\r\n","\n").Split('\n');
return new(relative,offset,lines.Skip(offset).Take(32).Select(s => s.Length>240 ? s[..240]+"..." : s).ToArray(),offset+32<lines.Length);
}
}
|