diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs index ab08241f..a6030603 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPlugin.cs @@ -7,4 +7,4 @@ namespace AIStudio.Tools.PluginSystem; /// /// The Lua state that the plugin was loaded into. /// The error message that occurred while parsing the plugin. -public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(state, PluginType.NONE, parsingError); \ No newline at end of file +public sealed class NoPlugin(LuaState state, string parsingError) : PluginBase(string.Empty, state, PluginType.NONE, parsingError); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs index 1553f74a..71549b33 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginBase.cs @@ -78,7 +78,7 @@ public abstract class PluginBase /// public bool IsValid => this is not NoPlugin && this.baseIssues.Count == 0 && this.pluginIssues.Count == 0; - protected PluginBase(LuaState state, PluginType type, string parseError = "") + protected PluginBase(string path, LuaState state, PluginType type, string parseError = "") { this.state = state; this.Type = type; @@ -93,6 +93,9 @@ public abstract class PluginBase this.state.OpenMathLibrary(); this.state.OpenBitwiseLibrary(); this.state.OpenCoroutineLibrary(); + + // Add the module loader so that the plugin can load other Lua modules: + this.state.ModuleLoader = new PluginLoader(path); var issues = new List(); if(!string.IsNullOrWhiteSpace(parseError)) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 9b1d1ebb..054d4e8d 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -90,7 +90,7 @@ public static class PluginFactory } - public static async Task Load(string code, CancellationToken cancellationToken = default) + public static async Task Load(string path, string code, CancellationToken cancellationToken = default) { var state = LuaState.Create(); @@ -114,7 +114,7 @@ public static class PluginFactory return type switch { - PluginType.LANGUAGE => new PluginLanguage(state, type), + PluginType.LANGUAGE => new PluginLanguage(path, state, type), _ => new NoPlugin(state, "This plugin type is not supported yet. Please try again with a future version of AI Studio.") }; diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs new file mode 100644 index 00000000..ec81f73c --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginLoader.cs @@ -0,0 +1,48 @@ +using System.Text; + +using AIStudio.Settings; + +using Lua; + +namespace AIStudio.Tools.PluginSystem; + +/// +/// Loads Lua modules from a plugin directory. +/// +/// +/// Any plugin can load Lua modules from its own directory. This class is used to load these modules. +/// Loading other modules outside the plugin directory is not allowed. +/// +/// The directory where the plugin is located. +public sealed class PluginLoader(string pluginDirectory) : ILuaModuleLoader +{ + private static readonly string PLUGIN_BASE_PATH = Path.Join(SettingsManager.DataDirectory, "plugins"); + + #region Implementation of ILuaModuleLoader + + /// + public bool Exists(string moduleName) + { + // Ensure that the user doesn't try to escape the plugin directory: + if (moduleName.Contains("..") || pluginDirectory.Contains("..")) + return false; + + // Ensure that the plugin directory is nested in the plugin base path: + if (!pluginDirectory.StartsWith(PLUGIN_BASE_PATH, StringComparison.OrdinalIgnoreCase)) + return false; + + var path = Path.Join(pluginDirectory, $"{moduleName}.lua"); + return File.Exists(path); + } + + /// + public async ValueTask LoadAsync(string moduleName, CancellationToken cancellationToken = default) + { + var path = Path.Join(pluginDirectory, $"{moduleName}.lua"); + var code = await File.ReadAllTextAsync(path, Encoding.UTF8, cancellationToken); + + return new(moduleName, code); + } + + #endregion +} \ No newline at end of file