Skip to content

Commit fe3ef20

Browse files
authored
Fix startup error when files are not readable. (#18)
1 parent 01162ec commit fe3ef20

File tree

6 files changed

+53
-14
lines changed

6 files changed

+53
-14
lines changed

OpenSSH_GUI.SshConfig/Extensions/SshConfigurationExtensions.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public static class SshConfigurationExtensions
2020
/// <paramref name="builder" />.
2121
/// </param>
2222
/// <returns>The <see cref="IConfigurationBuilder" />.</returns>
23-
public IConfigurationBuilder AddSshConfig(string path)
23+
public IConfigurationBuilder AddSshConfig(string path, Action<string, Exception>? loggingAction = null)
2424
{
25-
return builder.AddSshConfig(null, path, false, false);
25+
return builder.AddSshConfig(null, path, false, false, loggingAction);
2626
}
2727

2828
/// <summary>
@@ -34,9 +34,9 @@ public IConfigurationBuilder AddSshConfig(string path)
3434
/// </param>
3535
/// <param name="optional">Whether the file is optional.</param>
3636
/// <returns>The <see cref="IConfigurationBuilder" />.</returns>
37-
public IConfigurationBuilder AddSshConfig(string path, bool optional)
37+
public IConfigurationBuilder AddSshConfig(string path, bool optional, Action<string, Exception>? loggingAction = null)
3838
{
39-
return builder.AddSshConfig(null, path, optional, false);
39+
return builder.AddSshConfig(null, path, optional, false, loggingAction);
4040
}
4141

4242
/// <summary>
@@ -50,9 +50,9 @@ public IConfigurationBuilder AddSshConfig(string path, bool optional)
5050
/// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>
5151
/// <returns>The <see cref="IConfigurationBuilder" />.</returns>
5252
public IConfigurationBuilder AddSshConfig(string path, bool optional,
53-
bool reloadOnChange)
53+
bool reloadOnChange, Action<string, Exception>? loggingAction = null)
5454
{
55-
return builder.AddSshConfig(null, path, optional, reloadOnChange);
55+
return builder.AddSshConfig(null, path, optional, reloadOnChange, loggingAction);
5656
}
5757

5858
/// <summary>
@@ -67,7 +67,7 @@ public IConfigurationBuilder AddSshConfig(string path, bool optional,
6767
/// <param name="reloadOnChange">Whether the configuration should be reloaded if the file changes.</param>
6868
/// <returns>The <see cref="IConfigurationBuilder" />.</returns>
6969
public IConfigurationBuilder AddSshConfig(IFileProvider? fileProvider,
70-
string path, bool optional, bool reloadOnChange)
70+
string path, bool optional, bool reloadOnChange, Action<string, Exception>? loggingAction = null)
7171
{
7272
ArgumentNullException.ThrowIfNull(builder);
7373
ArgumentException.ThrowIfNullOrEmpty(path);
@@ -79,17 +79,20 @@ public IConfigurationBuilder AddSshConfig(IFileProvider? fileProvider,
7979
s.Optional = optional;
8080
s.ReloadOnChange = reloadOnChange;
8181
s.ResolveFileProvider();
82-
});
82+
}, loggingAction);
8383
}
8484

8585
/// <summary>
8686
/// Adds an SSH configuration source to the <paramref name="builder" />.
8787
/// </summary>
8888
/// <param name="configureSource">Configures the source.</param>
8989
/// <returns>The <see cref="IConfigurationBuilder" />.</returns>
90-
public IConfigurationBuilder AddSshConfig(Action<SshConfigurationSource>? configureSource)
90+
public IConfigurationBuilder AddSshConfig(Action<SshConfigurationSource>? configureSource, Action<string, Exception>? loggingAction)
9191
{
92-
var source = new SshConfigurationSource();
92+
var source = new SshConfigurationSource
93+
{
94+
OnSkippedIncludeFile = loggingAction
95+
};
9396
configureSource?.Invoke(source);
9497
return builder.Add(source);
9598
}

OpenSSH_GUI.SshConfig/Options/SshConfigParserOptions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ public sealed record SshConfigParserOptions
4040
/// Defaults to <see langword="false" />.
4141
/// </summary>
4242
public bool ThrowOnUnknownKey { get; init; }
43+
44+
/// <summary>
45+
/// Optional callback invoked when an included file cannot be read due to
46+
/// insufficient permissions or an I/O error. Receives the file path and the
47+
/// causing exception. When <see langword="null"/>, inaccessible files are
48+
/// silently skipped.
49+
/// </summary>
50+
public Action<string, Exception>? OnSkippedIncludeFile { get; init; }
4351

4452
/// <summary>Gets the default options instance.</summary>
4553
public static SshConfigParserOptions Default { get; } = new();

OpenSSH_GUI.SshConfig/Parsers/SshConfigParser.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,22 @@ private static SshConfigDocument ResolveInclude(
411411

412412
foreach (var file in Directory.GetFiles(dir, filePattern).Order(StringComparer.Ordinal))
413413
{
414-
var fileContent = File.ReadAllText(file);
414+
string fileContent;
415+
try
416+
{
417+
fileContent = File.ReadAllText(file);
418+
}
419+
catch (UnauthorizedAccessException ex)
420+
{
421+
options.OnSkippedIncludeFile?.Invoke(file, ex);
422+
continue;
423+
}
424+
catch (IOException ex)
425+
{
426+
options.OnSkippedIncludeFile?.Invoke(file, ex);
427+
continue;
428+
}
429+
415430
var included = ParseDocument(fileContent, file, options, depth + 1);
416431
globalItems.AddRange(included.GlobalItems);
417432
blocks.AddRange(included.Blocks);

OpenSSH_GUI.SshConfig/Services/SshConfigurationProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public override void Load(Stream stream)
3232
// We use the file path from the source if available for better error messages.
3333
var filePath = Source.Path;
3434
var document = SshConfigParser.Parse(content,
35-
new SshConfigParserOptions { IncludeBasePath = filePath is null ? null : Path.GetDirectoryName(filePath) });
35+
new SshConfigParserOptions { IncludeBasePath = filePath is null ? null : Path.GetDirectoryName(filePath), OnSkippedIncludeFile = Source is SshConfigurationSource source ? source.OnSkippedIncludeFile : null });
3636

3737
var data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
3838

OpenSSH_GUI.SshConfig/Services/SshConfigurationSource.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ namespace OpenSSH_GUI.SshConfig.Services;
77
/// </summary>
88
public sealed class SshConfigurationSource : FileConfigurationSource
99
{
10+
/// <summary>
11+
/// Optional callback invoked when an included file cannot be read due to
12+
/// insufficient permissions or an I/O error. Receives the file path and the
13+
/// causing exception. When <see langword="null"/>, inaccessible files are
14+
/// silently skipped.
15+
/// </summary>
16+
public Action<string, Exception>? OnSkippedIncludeFile { get; init; }
17+
1018
/// <summary>
1119
/// Builds the <see cref="SshConfigurationProvider" /> for this source.
1220
/// </summary>

OpenSSH_GUI/Program.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,15 @@ public static async Task Main(string[] args)
9999
private static void ConfigureAppConfiguration(HostBuilderContext builderContext,
100100
IConfigurationBuilder configurationBuilder)
101101
{
102-
configurationBuilder.AddSshConfig(ConfigFile.GetPathOfFile(), true, true);
103-
configurationBuilder.AddSshConfig(SshdConfig.GetPathOfFile(), true, true);
102+
configurationBuilder.AddSshConfig(ConfigFile.GetPathOfFile(), true, true, LoggingAction);
103+
configurationBuilder.AddSshConfig(SshdConfig.GetPathOfFile(), true, true, LoggingAction);
104104
configurationBuilder.AddInMemoryCollection([
105105
new KeyValuePair<string, string?>(VersionEnvVar, GetHostVersion())
106106
]);
107107
}
108+
109+
private static void LoggingAction(string arg1, Exception arg2)
110+
{
111+
Log.Logger.Error(arg2, "Failed to load SSH config file: {Path}", arg1);
112+
}
108113
}

0 commit comments

Comments
 (0)