Implemented a mechanism for excluding forbidden plugins

This commit is contained in:
Thorsten Sommer 2025-03-21 20:34:01 +01:00
parent 8fc6cb326b
commit 24f5127785
Signed by: tsommer
GPG Key ID: 371BBA77A02C0108
3 changed files with 110 additions and 0 deletions

View File

@ -0,0 +1,99 @@
namespace AIStudio.Tools.PluginSystem;
/// <summary>
/// Checks if a plugin is forbidden.
/// </summary>
public static class ForbiddenPlugins
{
private const string ID_PATTERN = "ID = \"";
private static readonly int ID_PATTERN_LEN = ID_PATTERN.Length;
/// <summary>
/// Checks if the given code represents a forbidden plugin.
/// </summary>
/// <param name="code">The code to check.</param>
/// <returns>The result of the check.</returns>
public static PluginCheckResult Check(ReadOnlySpan<char> code)
{
var endIndex = 0;
var foundAnyId = false;
var id = ReadOnlySpan<char>.Empty;
while (true)
{
// Create a slice of the code starting at the end index.
// This way we can search for all IDs in the code:
code = code[endIndex..];
// Read the next ID as a string:
if (!TryGetId(code, out id, out endIndex))
{
// When no ID was found at all, we block this plugin.
// When another ID was found previously, we allow this plugin.
if(foundAnyId)
return new PluginCheckResult(false, null);
return new PluginCheckResult(true, "No ID was found.");
}
// Try to parse the ID as a GUID:
if (!Guid.TryParse(id, out var parsedGuid))
{
// Again, when no ID was found at all, we block this plugin.
if(foundAnyId)
return new PluginCheckResult(false, null);
return new PluginCheckResult(true, "The ID is not a valid GUID.");
}
// Check if the GUID is forbidden:
if (FORBIDDEN_PLUGINS.TryGetValue(parsedGuid, out var reason))
return new PluginCheckResult(true, reason);
foundAnyId = true;
}
}
private static bool TryGetId(ReadOnlySpan<char> code, out ReadOnlySpan<char> id, out int endIndex)
{
//
// Please note: the code variable is a slice of the original code.
// That means the indices are relative to the slice, not the original code.
//
id = ReadOnlySpan<char>.Empty;
endIndex = 0;
// Find the next ID:
var idStartIndex = code.IndexOf(ID_PATTERN);
if (idStartIndex < 0)
return false;
// Find the start index of the value (Guid):
var valueStartIndex = idStartIndex + ID_PATTERN_LEN;
// Find the end index of the value. In order to do that,
// we create a slice of the code starting at the value
// start index. That means that the end index is relative
// to the inner slice, not the original code nor the outer slice.
var valueEndIndex = code[valueStartIndex..].IndexOf('"');
if (valueEndIndex < 0)
return false;
// From the perspective of the start index is the end index
// the length of the value:
endIndex = valueStartIndex + valueEndIndex;
id = code.Slice(valueStartIndex, valueEndIndex);
return true;
}
/// <summary>
/// The forbidden plugins.
/// </summary>
/// <remarks>
/// A dictionary that maps the GUID of a plugin to the reason why it is forbidden.
/// </remarks>
// ReSharper disable once CollectionNeverUpdated.Local
private static readonly Dictionary<Guid, string> FORBIDDEN_PLUGINS =
[
];
}

View File

@ -0,0 +1,8 @@
namespace AIStudio.Tools.PluginSystem;
/// <summary>
/// Represents the result of a plugin check.
/// </summary>
/// <param name="IsForbidden">In case the plugin is forbidden, this is true.</param>
/// <param name="Message">The message that describes why the plugin is forbidden.</param>
public readonly record struct PluginCheckResult(bool IsForbidden, string? Message);

View File

@ -92,6 +92,9 @@ public static class PluginFactory
public static async Task<PluginBase> Load(string path, string code, CancellationToken cancellationToken = default) public static async Task<PluginBase> Load(string path, string code, CancellationToken cancellationToken = default)
{ {
if(ForbiddenPlugins.Check(code) is { IsForbidden: true } forbiddenState)
return new NoPlugin($"This plugin is forbidden: {forbiddenState.Message}");
var state = LuaState.Create(); var state = LuaState.Create();
try try