diff --git a/OpenNest.Data/NestDefaults.cs b/OpenNest.Data/NestDefaults.cs index b0d4c20..66ae2ca 100644 --- a/OpenNest.Data/NestDefaults.cs +++ b/OpenNest.Data/NestDefaults.cs @@ -88,13 +88,10 @@ public sealed class NestDefaults var json = File.ReadAllText(path); dto = JsonSerializer.Deserialize(json, JsonOptions); } - catch (JsonException) - { - status = NestDefaultsStatus.Invalid; - return defaults; - } - catch (IOException) + catch (Exception ex) when (IsUnreadableFile(ex)) { + // A file that exists but cannot be read (locked, access-denied) + // or parsed must never block creating a nest. status = NestDefaultsStatus.Invalid; return defaults; } @@ -228,6 +225,14 @@ public sealed class NestDefaults } } + private static bool IsUnreadableFile(Exception ex) => + ex + is JsonException + or IOException + or UnauthorizedAccessException + or NotSupportedException + or System.Security.SecurityException; + private static bool IsValidSize(double width, double length) => !double.IsNaN(width) && !double.IsNaN(length) diff --git a/OpenNest.Tests/Data/NestDefaultsTests.cs b/OpenNest.Tests/Data/NestDefaultsTests.cs index db44927..f6b1004 100644 --- a/OpenNest.Tests/Data/NestDefaultsTests.cs +++ b/OpenNest.Tests/Data/NestDefaultsTests.cs @@ -75,6 +75,24 @@ public class NestDefaultsTests : IDisposable AssertFallback(loaded); } + [SkippableFact] + public void Load_ReadDeniedFile_ReturnsFallbackButReportsInvalid() + { + if (OperatingSystem.IsWindows()) + throw new SkipException("Unix file modes deny the read on Linux/macOS"); + + new NestDefaults { PartSpacing = 4 }.Save(_path); + File.SetUnixFileMode(_path, UnixFileMode.None); + Skip.If(CanRead(_path), "Process can read a mode-000 file (running as root)"); + + // The file is visible to File.Exists, but reading it is denied. + Assert.True(File.Exists(_path)); + var loaded = NestDefaults.Load(_path, out var status); + + Assert.Equal(NestDefaultsStatus.Invalid, status); + AssertFallback(loaded); + } + [Fact] public void Load_PartialFile_MergesPerField() { @@ -198,6 +216,19 @@ public class NestDefaultsTests : IDisposable Assert.Equal(new Size(48, 96), defaults.Size); } + private static bool CanRead(string path) + { + try + { + using var _ = File.OpenRead(path); + return true; + } + catch (UnauthorizedAccessException) + { + return false; + } + } + private static void AssertFallback(NestDefaults loaded) { Assert.Equal(Units.Inches, loaded.Units);