AquaLower names lowered .dsp files through StoreFileName; pin Windows forms; note limits

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Aqua 2026-11-07 10:38:28 +00:00
commit 5a77dc8e83
3 changed files with 35 additions and 2 deletions

View file

@ -13,6 +13,11 @@ namespace AquaSynth.Dsl;
/// rewritten names always do, so the two sets cannot meet; distinct ids stay distinct up to a 64-bit
/// hash. The result is never a path: no separators, never "." or "..".
/// </summary>
/// <remarks>
/// Not handled: symlinks and junctions already inside a store are followed, and Windows 8.3 short names
/// were not verified. An id with an uppercase letter is rewritten, so a batch directory named for such
/// an id gains a hash suffix.
/// </remarks>
public static class StoreFileName
{
private const int MaxStem = 100;

View file

@ -160,6 +160,33 @@ public sealed class StoreFileNameTests
Assert.Equal(Path.GetFullPath(root), Path.GetDirectoryName(full));
}
[Theory]
[InlineData("\\\\host\\share\\x")]
[InlineData("C:x")]
[InlineData("a:b")]
[InlineData("x.")]
[InlineData("x ")]
[InlineData("x. ")]
public void WindowsAbsoluteAndTrailingDotOrSpaceIdsStayContainedAndPlain(string id)
{
var root = Path.Combine(Path.GetTempPath(), "artifact-root-probe", "artifacts");
var name = StoreFileName.From(id);
Assert.Matches("^[A-Za-z0-9._-]+$", name);
Assert.False(name.EndsWith('.') || name.EndsWith(' '));
Assert.Equal(Path.GetFullPath(root), Path.GetDirectoryName(Path.GetFullPath(StoreFileName.Combine(root, id))));
}
[Theory]
[InlineData("../x")]
[InlineData("a/b")]
[InlineData("name:with:colons")]
public void LoweredDspFileNameStaysInTheOutputDirectory(string name)
{
var outDir = Path.Combine(Path.GetTempPath(), "aqualower-probe", "out");
var file = StoreFileName.From(name) + ".dsp";
Assert.Equal(Path.GetFullPath(outDir), Path.GetDirectoryName(Path.GetFullPath(Path.Combine(outDir, file))));
}
[Fact]
public void BareDotDotBatchIdIsRefused() =>
Assert.Throws<ArgumentException>(() => StoreFileName.Combine(Path.GetTempPath(), ".."));

View file

@ -47,8 +47,9 @@ foreach (var spec in args.Skip(2))
warnings.AddRange(export.Warnings);
}
else source = text;
File.WriteAllText(Path.Combine(outDir, name + ".dsp"), source);
entries.Add(new { name, origin = rel.Replace('\\', '/'), kind = path.EndsWith(".aqua", StringComparison.Ordinal) ? "aqua" : "faust", dsp = name + ".dsp", warnings });
var dspFile = StoreFileName.From(name) + ".dsp";
File.WriteAllText(Path.Combine(outDir, dspFile), source);
entries.Add(new { name, origin = rel.Replace('\\', '/'), kind = path.EndsWith(".aqua", StringComparison.Ordinal) ? "aqua" : "faust", dsp = dspFile, warnings });
Console.WriteLine($"{name}: {source.Length} chars from {rel}, {warnings.Count} warnings");
}
File.WriteAllText(Path.Combine(outDir, "catalog.json"), JsonSerializer.Serialize(entries, new JsonSerializerOptions { WriteIndented = true }));