Skip to content

Commit

Permalink
Safely handle misconfigured extensions (#1161)
Browse files Browse the repository at this point in the history
  • Loading branch information
veler committed May 26, 2024
1 parent 0cdb707 commit 8d4ea06
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 29 deletions.
10 changes: 7 additions & 3 deletions src/app/dev/DevToys.Business/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,22 +200,26 @@ internal async Task RunSmartDetectionAsync(bool isInCompactOverlayMode, bool isD

try
{
using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(2));
using var cancellationTokenSource
= new CancellationTokenSource(
Debugger.IsAttached ? TimeSpan.FromHours(1) : TimeSpan.FromSeconds(2));

CancellationToken cancellationToken = cancellationTokenSource.Token;
INotifyPropertyChanged? selectedMenuBeforeSmartDetection = SelectedMenuItem;
Guard.IsNotNull(selectedMenuBeforeSmartDetection);

// Retrieve clipboard content.
object? rawClipboardData = await _clipboard.GetClipboardDataAsync();

// If the clipboard content has changed since the last time
if (!cancellationTokenSource.Token.IsCancellationRequested && !AreOldAndNewClipboardDataEqual(_oldRawClipboardData, rawClipboardData))
if (!cancellationToken.IsCancellationRequested && !AreOldAndNewClipboardDataEqual(_oldRawClipboardData, rawClipboardData))
{
// Reset recommended tools.
_guiToolProvider.ForEachToolViewItem(toolViewItem => toolViewItem.IsRecommended = false);

// Detect tools to recommend.
IReadOnlyList<SmartDetectedTool> detectedTools
= await _smartDetectionService.DetectAsync(rawClipboardData, strict: true, cancellationTokenSource.Token)
= await _smartDetectionService.DetectAsync(rawClipboardData, strict: true, cancellationToken)
.ConfigureAwait(true);

GuiToolViewItem? firstToolViewItem = null;
Expand Down
27 changes: 23 additions & 4 deletions src/app/dev/DevToys.Core/Tools/GuiToolInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal GuiToolInstance(Lazy<IGuiTool, GuiToolMetadata> guiToolDefinition, Asse

public string LongDisplayTitle => _longDisplayTitle.Value;

public string LongOrShortDisplayTitle => string.IsNullOrWhiteSpace(_longDisplayTitle.Value) ? ShortDisplayTitle : _longDisplayTitle.Value;
public string LongOrShortDisplayTitle => string.IsNullOrWhiteSpace(LongDisplayTitle) ? ShortDisplayTitle : LongDisplayTitle;

public string Description => _descriptionDisplayTitle.Value;

Expand Down Expand Up @@ -164,9 +164,25 @@ public void RebuildView()

private string GetDisplayString(string resourceName)
{
return _resourceManager.Value is not null && !string.IsNullOrWhiteSpace(resourceName)
? _resourceManager.Value.GetString(resourceName) ?? string.Empty
: string.Empty;
if (_resourceManager.Value is null)
{
LogGetMetadataStringFailed(resourceName, InternalComponentName);
return $"[Unable to get the text for '{resourceName}', " +
$"likely because we couldn't find a proper '{nameof(IResourceAssemblyIdentifier)}' " +
$"for the tool '{_guiToolDefinition.Metadata.InternalComponentName}'.]";
}

try
{
return _resourceManager.Value is not null && !string.IsNullOrWhiteSpace(resourceName)
? _resourceManager.Value.GetString(resourceName) ?? $"[Unable to find '{resourceName}' in '{_guiToolDefinition.Metadata.ResourceManagerBaseName}']"
: string.Empty;
}
catch
{
LogGetMetadataStringFailed(resourceName, InternalComponentName);
return $"[Unable to find '{resourceName}' in '{_guiToolDefinition.Metadata.ResourceManagerBaseName}']";
}
}

[LoggerMessage(0, LogLevel.Information, "Initialized '{toolName}' tool instance manager.")]
Expand All @@ -183,4 +199,7 @@ private string GetDisplayString(string resourceName)

[LoggerMessage(4, LogLevel.Error, "Unexpectedly failed to dispose '{toolName}'.")]
partial void LogDisposingToolFailed(Exception ex, string toolName);

[LoggerMessage(5, LogLevel.Error, "Unable to get the string for '{metadataName}' for the tool '{toolName}'.")]
partial void LogGetMetadataStringFailed(string metadataName, string toolName);
}
19 changes: 13 additions & 6 deletions src/app/dev/DevToys.Core/Tools/GuiToolProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ private void BuildGuiToolInstances(
Assembly? resourceManagerAssembly = null;
if (!string.IsNullOrEmpty(guiToolDefinition.Metadata.ResourceManagerAssemblyIdentifier))
{
resourceManagerAssembly = GetResourceManagerAssembly(guiToolDefinition.Metadata.ResourceManagerAssemblyIdentifier);
resourceManagerAssembly = GetResourceManagerAssembly(guiToolDefinition.Metadata);
}

// Create the tool instance and store it in a menu placement list.
Expand Down Expand Up @@ -584,8 +584,8 @@ private IEnumerable<INotifyPropertyChanged> BuildAllToolsTreeViewItems()

if (toolGroup is null)
{
ThrowHelper.ThrowInvalidDataException($"Unable to find the group named '{tool.GroupName}' that the tool '{tool.InternalComponentName}' is pointing to.");
return null;
LogGroupNotFound(tool.GroupName, tool.InternalComponentName);
continue;
}

// Create a group view presentation, if needed.
Expand Down Expand Up @@ -619,17 +619,18 @@ IEnumerable<GroupViewItem> orderedGroups
return groups.Values.Select(g => g.Value);
}

private Assembly GetResourceManagerAssembly(string resourceManagerAssemblyIdentifier)
private Assembly? GetResourceManagerAssembly(GuiToolMetadata guiToolMetadata)
{
foreach (Lazy<IResourceAssemblyIdentifier, ResourceAssemblyIdentifierMetadata> item in _resourceAssemblyIdentifiers)
{
if (string.Equals(item.Metadata.InternalComponentName, resourceManagerAssemblyIdentifier, StringComparison.Ordinal))
if (string.Equals(item.Metadata.InternalComponentName, guiToolMetadata.ResourceManagerAssemblyIdentifier, StringComparison.Ordinal))
{
return item.Value.GetType().Assembly;
}
}

throw new InvalidDataException($"Unable to find the {nameof(ToolDisplayInformationAttribute.ResourceManagerAssemblyIdentifier)} '{resourceManagerAssemblyIdentifier}'.");
LogResourceManagerAssemblyIdentifierFound(guiToolMetadata.InternalComponentName, guiToolMetadata.ResourceManagerAssemblyIdentifier);
return null;
}

private GroupViewItem CreateFavoriteGroupViewItem(IReadOnlyList<GuiToolViewItem> favoriteTools)
Expand Down Expand Up @@ -777,4 +778,10 @@ private static TextSpan[] CleanUpMatchedSpans(List<TextSpan>? spans)

[LoggerMessage(3, LogLevel.Information, "Instantiated {toolCount} tools in {duration}ms")]
partial void LogToolInstancesCreated(int toolCount, double duration);

[LoggerMessage(4, LogLevel.Error, "Unable to find the group named '{groupName}' that the tool '{toolName}' is pointing to.")]
partial void LogGroupNotFound(string groupName, string toolName);

[LoggerMessage(5, LogLevel.Error, "Unable to find the ResourceManagerAssemblyIdentifier '{resourceManagerAssemblyIdentifier}' that the tool '{toolName}' is pointing to.")]
partial void LogResourceManagerAssemblyIdentifierFound(string toolName, string resourceManagerAssemblyIdentifier);
}
31 changes: 24 additions & 7 deletions src/app/dev/DevToys.Core/Tools/SmartDetectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public SmartDetectionService(
_detectorHierarchy = BuildDetectorNodeHierarchy(dataTypeDetectors);

// Create a map of data types to tools.
_dataTypeToToolInstanceMap = BuildDataTypeToToolInstanceMap(guiToolProvider);
_dataTypeToToolInstanceMap = BuildDataTypeToToolInstanceMap(guiToolProvider, dataTypeDetectors);
}

/// <summary>
Expand Down Expand Up @@ -231,8 +231,11 @@ var lookup
/// Builds a dictionary that maps data types to a list of GUI tool instances that accept them.
/// </summary>
/// <param name="guiToolProvider">The GUI tool provider.</param>
/// <param name="detectors">The collection of detectors.</param>
/// <returns>A dictionary that maps data types to a list of GUI tool instances that accept them.</returns>
private static Dictionary<string, List<GuiToolInstance>> BuildDataTypeToToolInstanceMap(GuiToolProvider guiToolProvider)
private Dictionary<string, List<GuiToolInstance>> BuildDataTypeToToolInstanceMap(
GuiToolProvider guiToolProvider,
IEnumerable<Lazy<IDataTypeDetector, DataTypeDetectorMetadata>> dataTypeDetectors)
{
// Create a new dictionary with case-insensitive string keys
var dataTypeToToolInstanceMap = new Dictionary<string, List<GuiToolInstance>>(StringComparer.OrdinalIgnoreCase);
Expand All @@ -246,13 +249,24 @@ private static Dictionary<string, List<GuiToolInstance>> BuildDataTypeToToolInst
for (int j = 0; j < tool.AcceptedDataTypeNames.Count; j++)
{
string dataType = tool.AcceptedDataTypeNames[j];
if (!dataTypeToToolInstanceMap.TryGetValue(dataType, out List<GuiToolInstance>? toolList))

if (!string.IsNullOrWhiteSpace(dataType))
{
toolList = new();
dataTypeToToolInstanceMap[dataType] = toolList;
}
if (dataTypeDetectors.Any(detector => string.Equals(detector.Metadata.DataTypeName, dataType, StringComparison.CurrentCultureIgnoreCase)))
{
if (!dataTypeToToolInstanceMap.TryGetValue(dataType, out List<GuiToolInstance>? toolList))
{
toolList = new();
dataTypeToToolInstanceMap[dataType] = toolList;
}

toolList.Add(tool);
toolList.Add(tool);
}
else
{
LogDataDetectorNotFound(tool.InternalComponentName, dataType);
}
}
}
}

Expand All @@ -265,6 +279,9 @@ private static Dictionary<string, List<GuiToolInstance>> BuildDataTypeToToolInst
[LoggerMessage(1, LogLevel.Error, $"Error while building data detectors.")]
partial void LogBuildDetectorNodeHierarchyError(Exception ex);

[LoggerMessage(2, LogLevel.Warning, "Unable to find the data type detector '{dataTypeName}' for the tool '{toolName}'.")]
partial void LogDataDetectorNotFound(string toolName, string dataTypeName);

private sealed class DetectorNode
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@
using System.Reflection;
using System.Resources;
using DevToys.Api;
using Microsoft.Extensions.Logging;

namespace DevToys.CLI;

internal sealed class CommandToICommandLineToolMap
internal sealed partial class CommandToICommandLineToolMap
{
private readonly ILogger _logger;

internal CommandToICommandLineToolMap(ICommandLineTool commandLineTool, CommandLineToolMetadata metadata)
{
Guard.IsNotNull(commandLineTool);
Guard.IsNotNull(metadata);

_logger = this.Log();

// Get the resource manager, if possible.
ResourceManager? resourceManager = GetResourceManager(commandLineTool, metadata);

Expand Down Expand Up @@ -61,13 +66,31 @@ internal CommandToICommandLineToolMap(ICommandLineTool commandLineTool, CommandL
return resourceManager;
}

private static string GetCommandDescription(ResourceManager? resourceManager, CommandLineToolMetadata metadata)
private string GetCommandDescription(ResourceManager? resourceManager, CommandLineToolMetadata metadata)
{
string? commandDescription
= resourceManager is not null && !string.IsNullOrWhiteSpace(metadata.DescriptionResourceName)
? resourceManager.GetString(metadata.DescriptionResourceName) ?? string.Empty
: string.Empty;
if (resourceManager is null)
{
LogGetMetadataStringFailed(metadata.DescriptionResourceName, metadata.InternalComponentName);
return $"[Unable to get the text for '{metadata.DescriptionResourceName}', " +
$"likely because we couldn't find a proper '{nameof(IResourceAssemblyIdentifier)}' " +
$"for the tool '{metadata.InternalComponentName}'.]";
}

return commandDescription;
try
{
string? commandDescription
= !string.IsNullOrWhiteSpace(metadata.DescriptionResourceName)
? resourceManager.GetString(metadata.DescriptionResourceName) ?? $"[Unable to find '{metadata.DescriptionResourceName}' in '{metadata.ResourceManagerBaseName}']"
: string.Empty;
return commandDescription;
}
catch
{
LogGetMetadataStringFailed(metadata.DescriptionResourceName, metadata.InternalComponentName);
return $"[Unable to find '{metadata.DescriptionResourceName}' in '{metadata.ResourceManagerBaseName}']";
}
}

[LoggerMessage(0, LogLevel.Error, "Unable to get the string for '{metadataName}' for the tool '{toolName}'.")]
partial void LogGetMetadataStringFailed(string metadataName, string toolName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
using System.Resources;
using DevToys.Api;
using DevToys.CLI.Core;
using Microsoft.Extensions.Logging;

namespace DevToys.CLI;

internal sealed class OptionToICommandLineToolMap
internal sealed partial class OptionToICommandLineToolMap
{
private readonly ILogger _logger;
private static readonly Type optionType = typeof(Option<>);

private readonly ICommandLineTool _commandLineTool;
Expand All @@ -24,6 +26,7 @@ internal OptionToICommandLineToolMap(
Guard.IsNotNull(property);
Guard.IsNotNull(commandLineOptionAttribute);

_logger = this.Log();
_property = property;
_commandLineTool = commandLineTool;

Expand Down Expand Up @@ -59,7 +62,7 @@ internal void SetPropertyValue(object? value)
_property.SetValue(_commandLineTool, value);
}

private static string? GetOptionDescription(ICommandLineTool commandLineTool, CommandLineOptionAttribute commandLineOptionAttribute, ResourceManager? parentResourceManager)
private string? GetOptionDescription(ICommandLineTool commandLineTool, CommandLineOptionAttribute commandLineOptionAttribute, ResourceManager? parentResourceManager)
{
string? optionDescription = null;
if (!string.IsNullOrWhiteSpace(commandLineOptionAttribute.DescriptionResourceName))
Expand All @@ -78,6 +81,12 @@ internal void SetPropertyValue(object? value)
{
optionDescription = optionResourceManager.GetString(commandLineOptionAttribute.DescriptionResourceName);
}

if (optionDescription is null)
{
LogGetMetadataStringFailed(commandLineOptionAttribute.DescriptionResourceName);
optionDescription = $"[Unable to find '{commandLineOptionAttribute.DescriptionResourceName}' in '{commandLineOptionAttribute.ResourceManagerBaseName}']";
}
}

return optionDescription;
Expand Down Expand Up @@ -223,4 +232,7 @@ private static OneOfOption CreateOneOfTypeOption(
result.ErrorMessage = result.LocalizationResources.ArgumentConversionCannotParse(result.ToString(), typeof(OneOfOption));
return null;
}

[LoggerMessage(0, LogLevel.Error, "Unable to get the string for '{metadataName}'.")]
partial void LogGetMetadataStringFailed(string metadataName);
}
5 changes: 5 additions & 0 deletions src/app/dev/platforms/desktop/DevToys.CLI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ private static void Main(string[] args)

LogAppShuttingDown(logger);
loggerFactory.Dispose();

if (Debugger.IsAttached)
{
Console.ReadKey();
}
}

private static async Task MainAsync(string[] args)
Expand Down

0 comments on commit 8d4ea06

Please sign in to comment.