StoreFileName.Combine: batch ids from the command line cannot leave the artifact root
IpaTrialWorker song-score took --batch-id raw into Path.Combine; the orchestrator sites share the new Combine. Tests cover traversal and absolute batch ids and the underscore on device stems (nul.x, LPT1.x); drop the double From on the emitter name. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
afd0cc9ec3
commit
80ebf39033
4 changed files with 26 additions and 4 deletions
|
|
@ -48,6 +48,9 @@ public static class StoreFileName
|
|||
return $"{safe}-{hash}";
|
||||
}
|
||||
|
||||
/// <summary>A child of <paramref name="root"/> named for an external id; the result never leaves the root.</summary>
|
||||
public static string Combine(string root, string id) => Path.Combine(root, From(id));
|
||||
|
||||
private static bool IsFixedPoint(string id, string safe)
|
||||
{
|
||||
if (safe != id || id.EndsWith('.') || id.Any(char.IsAsciiLetterUpper) || IsDeviceStem(id) || EndsWithHashShape(id))
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ public static class IpaTrialOrchestrator
|
|||
throw new ArgumentException("At least one IPA trial target set is required.", nameof(trialSets));
|
||||
}
|
||||
|
||||
var batchDirectory = Path.Combine(artifactRoot, StoreFileName.From(options.BatchId));
|
||||
var batchDirectory = StoreFileName.Combine(artifactRoot, options.BatchId);
|
||||
Directory.CreateDirectory(batchDirectory);
|
||||
var storePath = Path.Combine(batchDirectory, options.TrialResultStoreFileName);
|
||||
var referenceRenderer = new PinkTromboneReferenceRenderer();
|
||||
|
|
@ -156,7 +156,7 @@ public static class IpaTrialOrchestrator
|
|||
var candidateDir = Path.Combine(round.RoundDirectory, "audio", candidate.Id);
|
||||
Directory.CreateDirectory(candidateDir);
|
||||
var script = await File.ReadAllTextAsync(candidate.ScriptPath, cancellationToken).ConfigureAwait(false);
|
||||
var source = FaustEmitter.EmitScript(script, new FaustExportOptions(StoreFileName.From(candidate.Id))).Source;
|
||||
var source = FaustEmitter.EmitScript(script, new FaustExportOptions(candidate.Id)).Source;
|
||||
var dspPath = Path.Combine(candidateDir, "candidate.dsp");
|
||||
await File.WriteAllTextAsync(dspPath, source, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ public static class IpaTrialOrchestrator
|
|||
throw new ArgumentException("At least one IPA script candidate is required.", nameof(candidates));
|
||||
}
|
||||
|
||||
var batchDirectory = Path.Combine(artifactRoot, StoreFileName.From(options.BatchId));
|
||||
var batchDirectory = StoreFileName.Combine(artifactRoot, options.BatchId);
|
||||
var candidateRoot = Path.Combine(batchDirectory, "agent-candidates");
|
||||
var timelineRoot = Path.Combine(batchDirectory, "agent-timelines");
|
||||
Directory.CreateDirectory(candidateRoot);
|
||||
|
|
|
|||
|
|
@ -43,9 +43,12 @@ public sealed class StoreFileNameTests
|
|||
[InlineData("COM9.f32")]
|
||||
[InlineData("lpt0")]
|
||||
[InlineData("prn.cc")]
|
||||
[InlineData("nul.x")]
|
||||
[InlineData("LPT1.x")]
|
||||
public void WindowsDeviceStemsAreRewritten(string id)
|
||||
{
|
||||
var name = StoreFileName.From(id);
|
||||
Assert.StartsWith("_", name);
|
||||
Assert.NotEqual(id.ToLowerInvariant(), name.ToLowerInvariant());
|
||||
Assert.Matches("-[0-9a-f]{16}$", name);
|
||||
var stem = name.Split('.')[0].TrimEnd(' ');
|
||||
|
|
@ -115,5 +118,21 @@ public sealed class StoreFileNameTests
|
|||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("../x")]
|
||||
[InlineData("..\\x")]
|
||||
[InlineData("/etc/x")]
|
||||
[InlineData("C:\\x")]
|
||||
public void BatchIdsFromTheCommandLineCannotLeaveTheArtifactRoot(string batchId)
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "artifact-root-probe", "artifacts");
|
||||
var full = Path.GetFullPath(StoreFileName.Combine(root, batchId));
|
||||
Assert.Equal(Path.GetFullPath(root), Path.GetDirectoryName(full));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BareDotDotBatchIdIsRefused() =>
|
||||
Assert.Throws<ArgumentException>(() => StoreFileName.Combine(Path.GetTempPath(), ".."));
|
||||
|
||||
private static string UriPath(string uri) => new Uri(uri).LocalPath;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ static async Task SongScoreAsync(Dictionary<string, string> options)
|
|||
throw new InvalidDataException($"No .aqua song candidates found under `{patchRoot}`.");
|
||||
}
|
||||
|
||||
var batchDirectory = Path.Combine(artifactRoot, batchId);
|
||||
var batchDirectory = StoreFileName.Combine(artifactRoot, batchId);
|
||||
var candidateRoot = Path.Combine(batchDirectory, "song-candidates");
|
||||
var timelineRoot = Path.Combine(batchDirectory, "song-timelines");
|
||||
Directory.CreateDirectory(candidateRoot);
|
||||
|
|
|
|||
Loading…
Reference in a new issue