diff --git a/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs b/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs index 53fa7a1..ca8d72e 100644 --- a/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs +++ b/OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs @@ -20,9 +20,9 @@ public static class SshConfigurationExtensions /// . /// /// The . - public IConfigurationBuilder AddSshConfig(string path) + public IConfigurationBuilder AddSshConfig(string path, Action? loggingAction = null) { - return builder.AddSshConfig(null, path, false, false); + return builder.AddSshConfig(null, path, false, false, loggingAction); } /// @@ -34,9 +34,9 @@ public IConfigurationBuilder AddSshConfig(string path) /// /// Whether the file is optional. /// The . - public IConfigurationBuilder AddSshConfig(string path, bool optional) + public IConfigurationBuilder AddSshConfig(string path, bool optional, Action? loggingAction = null) { - return builder.AddSshConfig(null, path, optional, false); + return builder.AddSshConfig(null, path, optional, false, loggingAction); } /// @@ -50,9 +50,9 @@ public IConfigurationBuilder AddSshConfig(string path, bool optional) /// Whether the configuration should be reloaded if the file changes. /// The . public IConfigurationBuilder AddSshConfig(string path, bool optional, - bool reloadOnChange) + bool reloadOnChange, Action? loggingAction = null) { - return builder.AddSshConfig(null, path, optional, reloadOnChange); + return builder.AddSshConfig(null, path, optional, reloadOnChange, loggingAction); } /// @@ -67,7 +67,7 @@ public IConfigurationBuilder AddSshConfig(string path, bool optional, /// Whether the configuration should be reloaded if the file changes. /// The . public IConfigurationBuilder AddSshConfig(IFileProvider? fileProvider, - string path, bool optional, bool reloadOnChange) + string path, bool optional, bool reloadOnChange, Action? loggingAction = null) { ArgumentNullException.ThrowIfNull(builder); ArgumentException.ThrowIfNullOrEmpty(path); @@ -79,7 +79,7 @@ public IConfigurationBuilder AddSshConfig(IFileProvider? fileProvider, s.Optional = optional; s.ReloadOnChange = reloadOnChange; s.ResolveFileProvider(); - }); + }, loggingAction); } /// @@ -87,9 +87,12 @@ public IConfigurationBuilder AddSshConfig(IFileProvider? fileProvider, /// /// Configures the source. /// The . - public IConfigurationBuilder AddSshConfig(Action? configureSource) + public IConfigurationBuilder AddSshConfig(Action? configureSource, Action? loggingAction) { - var source = new SshConfigurationSource(); + var source = new SshConfigurationSource + { + OnSkippedIncludeFile = loggingAction + }; configureSource?.Invoke(source); return builder.Add(source); } diff --git a/OpenSSH_GUI.SshConfig/OpenSSH_GUI.SshConfig.csproj b/OpenSSH_GUI.SshConfig/OpenSSH_GUI.SshConfig.csproj index 0042c3b..ed5af58 100644 --- a/OpenSSH_GUI.SshConfig/OpenSSH_GUI.SshConfig.csproj +++ b/OpenSSH_GUI.SshConfig/OpenSSH_GUI.SshConfig.csproj @@ -3,7 +3,6 @@ false true - true true OpenSSH_GUI.SshConfig diff --git a/OpenSSH_GUI.SshConfig/Options/SshConfigParserOptions.cs b/OpenSSH_GUI.SshConfig/Options/SshConfigParserOptions.cs index 2108b52..0f739db 100644 --- a/OpenSSH_GUI.SshConfig/Options/SshConfigParserOptions.cs +++ b/OpenSSH_GUI.SshConfig/Options/SshConfigParserOptions.cs @@ -40,6 +40,14 @@ public sealed record SshConfigParserOptions /// Defaults to . /// public bool ThrowOnUnknownKey { get; init; } + + /// + /// Optional callback invoked when an included file cannot be read due to + /// insufficient permissions or an I/O error. Receives the file path and the + /// causing exception. When , inaccessible files are + /// silently skipped. + /// + public Action? OnSkippedIncludeFile { get; init; } /// Gets the default options instance. public static SshConfigParserOptions Default { get; } = new(); diff --git a/OpenSSH_GUI.SshConfig/Parsers/SshConfigParser.cs b/OpenSSH_GUI.SshConfig/Parsers/SshConfigParser.cs index 5d5101a..0b5fa79 100644 --- a/OpenSSH_GUI.SshConfig/Parsers/SshConfigParser.cs +++ b/OpenSSH_GUI.SshConfig/Parsers/SshConfigParser.cs @@ -411,7 +411,22 @@ private static SshConfigDocument ResolveInclude( foreach (var file in Directory.GetFiles(dir, filePattern).Order(StringComparer.Ordinal)) { - var fileContent = File.ReadAllText(file); + string fileContent; + try + { + fileContent = File.ReadAllText(file); + } + catch (UnauthorizedAccessException ex) + { + options.OnSkippedIncludeFile?.Invoke(file, ex); + continue; + } + catch (IOException ex) + { + options.OnSkippedIncludeFile?.Invoke(file, ex); + continue; + } + var included = ParseDocument(fileContent, file, options, depth + 1); globalItems.AddRange(included.GlobalItems); blocks.AddRange(included.Blocks); diff --git a/OpenSSH_GUI.SshConfig/Services/SshConfigurationProvider.cs b/OpenSSH_GUI.SshConfig/Services/SshConfigurationProvider.cs index 66a328d..f2b78db 100644 --- a/OpenSSH_GUI.SshConfig/Services/SshConfigurationProvider.cs +++ b/OpenSSH_GUI.SshConfig/Services/SshConfigurationProvider.cs @@ -32,7 +32,7 @@ public override void Load(Stream stream) // We use the file path from the source if available for better error messages. var filePath = Source.Path; var document = SshConfigParser.Parse(content, - new SshConfigParserOptions { IncludeBasePath = filePath is null ? null : Path.GetDirectoryName(filePath) }); + new SshConfigParserOptions { IncludeBasePath = filePath is null ? null : Path.GetDirectoryName(filePath), OnSkippedIncludeFile = Source is SshConfigurationSource source ? source.OnSkippedIncludeFile : null }); var data = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/OpenSSH_GUI.SshConfig/Services/SshConfigurationSource.cs b/OpenSSH_GUI.SshConfig/Services/SshConfigurationSource.cs index 56893e2..78a92b2 100644 --- a/OpenSSH_GUI.SshConfig/Services/SshConfigurationSource.cs +++ b/OpenSSH_GUI.SshConfig/Services/SshConfigurationSource.cs @@ -7,6 +7,14 @@ namespace OpenSSH_GUI.SshConfig.Services; /// public sealed class SshConfigurationSource : FileConfigurationSource { + /// + /// Optional callback invoked when an included file cannot be read due to + /// insufficient permissions or an I/O error. Receives the file path and the + /// causing exception. When , inaccessible files are + /// silently skipped. + /// + public Action? OnSkippedIncludeFile { get; init; } + /// /// Builds the for this source. /// diff --git a/OpenSSH_GUI/Program.cs b/OpenSSH_GUI/Program.cs index 86e6ace..76b4bc7 100644 --- a/OpenSSH_GUI/Program.cs +++ b/OpenSSH_GUI/Program.cs @@ -99,10 +99,15 @@ public static async Task Main(string[] args) private static void ConfigureAppConfiguration(HostBuilderContext builderContext, IConfigurationBuilder configurationBuilder) { - configurationBuilder.AddSshConfig(ConfigFile.GetPathOfFile(), true, true); - configurationBuilder.AddSshConfig(SshdConfig.GetPathOfFile(), true, true); + configurationBuilder.AddSshConfig(ConfigFile.GetPathOfFile(), true, true, LoggingAction); + configurationBuilder.AddSshConfig(SshdConfig.GetPathOfFile(), true, true, LoggingAction); configurationBuilder.AddInMemoryCollection([ new KeyValuePair(VersionEnvVar, GetHostVersion()) ]); } + + private static void LoggingAction(string arg1, Exception arg2) + { + Log.Logger.Error(arg2, "Failed to load SSH config file: {Path}", arg1); + } } \ No newline at end of file diff --git a/appimage/io.github.frequency403.openssh-gui.metainfo.xml b/appimage/io.github.frequency403.openssh_gui.metainfo.xml similarity index 97% rename from appimage/io.github.frequency403.openssh-gui.metainfo.xml rename to appimage/io.github.frequency403.openssh_gui.metainfo.xml index b2f2b9d..bdd59df 100644 --- a/appimage/io.github.frequency403.openssh-gui.metainfo.xml +++ b/appimage/io.github.frequency403.openssh_gui.metainfo.xml @@ -33,7 +33,7 @@ - opensshgui + openssh-gui