source: branches/eraser6/Manager/Plugins.cs @ 569

Revision 569, 9.4 KB checked in by lowjoel, 6 years ago (diff)

Tighten the plugin loading criteria:
-Plugins are first loaded reflection-only, checking for the presence of the IPlugin interface being implemented. If it is not implemented, the DLL is skipped.
-The plugins are then checked for a public key. If the public key is different from the executing assembly, it is not trusted and therefore is foreign.
-Foreign plugins must be approved by the user explicitly before they are loaded.

The SettingsPanel? and the ManagerSettings? classes have therefore been updated.

  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25using System.IO;
26using System.Reflection;
27using System.Windows.Forms;
28using System.Runtime.InteropServices;
29
30namespace Eraser.Manager.Plugin
31{
32    /// <summary>
33    /// The plugins host interface which is used for communicating with the host
34    /// program.
35    /// </summary>
36    /// <remarks>Remember to call Load to load the plugins into memory, otherwise
37    /// they will never be loaded.</remarks>
38    public abstract class Host : IDisposable
39    {
40        /// <summary>
41        /// Getter that retrieves the global plugin host instance.
42        /// </summary>
43        public static Host Instance
44        {
45            get { return ManagerLibrary.Instance.Host; }
46        }
47
48        /// <summary>
49        /// Retrieves the list of currently loaded plugins.
50        /// </summary>
51        public abstract List<PluginInstance> Plugins
52        {
53            get;
54        }
55
56        /// <summary>
57        /// Loads all plugins into memory.
58        /// </summary>
59        public abstract void Load();
60
61        /// <summary>
62        /// Cleans up resources used by the host. Also unloads all loaded plugins.
63        /// </summary>
64        public abstract void Dispose();
65
66        /// <summary>
67        /// The plugin load event delegate.
68        /// </summary>
69        /// <param name="instance">The instance of the plugin loaded.</param>
70        public delegate void PluginLoadedFunction(PluginInstance instance);
71
72        /// <summary>
73        /// The plugin loaded event.
74        /// </summary>
75        public event PluginLoadedFunction PluginLoaded;
76
77        /// <summary>
78        /// Event callback executor for the OnPluginLoad Event
79        /// </summary>
80        /// <param name="instance"></param>
81        protected void OnPluginLoaded(PluginInstance instance)
82        {
83            if (PluginLoaded != null)
84                PluginLoaded(instance);
85        }
86
87        /// <summary>
88        /// Loads a plugin.
89        /// </summary>
90        /// <param name="filePath">The absolute or relative file path to the
91        /// DLL.</param>
92        public abstract void LoadPlugin(string filePath);
93    }
94
95    /// <summary>
96    /// The default plugins host implementation.
97    /// </summary>
98    internal class DefaultHost : Host
99    {
100        /// <summary>
101        /// Constructor. Loads all plugins in the Plugins folder.
102        /// </summary>
103        public DefaultHost()
104        {
105        }
106
107        public override void Load()
108        {
109            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
110            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
111            string pluginsFolder = Path.Combine(
112                Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
113                PLUGINSFOLDER //Plugins folder
114            );
115
116            foreach (string fileName in Directory.GetFiles(pluginsFolder))
117            {
118                FileInfo file = new FileInfo(fileName);
119                if (file.Extension.Equals(".dll"))
120                    try
121                    {
122                        LoadPlugin(file.FullName);
123                    }
124                    catch (BadImageFormatException)
125                    {
126                    }
127                    catch (FileLoadException)
128                    {
129                    }
130            }
131        }
132
133        public override void Dispose()
134        {
135            //Unload all the plugins. This will cause all the plugins to execute
136            //the cleanup code.
137            foreach (PluginInstance plugin in plugins)
138                plugin.Plugin.Dispose();
139        }
140
141        /// <summary>
142        /// The path to the folder containing the plugins.
143        /// </summary>
144        public const string PLUGINSFOLDER = "Plugins";
145
146        public override List<PluginInstance> Plugins
147        {
148            get { return plugins; }
149        }
150
151        public override void LoadPlugin(string filePath)
152        {
153            //Create the PluginInstance structure
154            Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath);
155            PluginInstance instance = new PluginInstance(reflectAssembly, null);
156            Type typePlugin = null;
157
158            //Iterate over every exported type, checking if it implements IPlugin
159            foreach (Type type in instance.Assembly.GetExportedTypes())
160            {
161                //Check for an implementation of IPlugin
162                Type typeInterface = type.GetInterface("Eraser.Manager.Plugin.IPlugin", true);
163                if (typeInterface != null)
164                {
165                    typePlugin = type;
166                    break;
167                }
168            }
169
170            //If the typePlugin type is empty the assembly doesn't implement IPlugin; we
171            //aren't interested.
172            if (typePlugin == null)
173                return;
174
175            //OK this assembly is a plugin
176            lock (plugins)
177                plugins.Add(instance);
178
179            //First check the plugin for the presence of a signature.
180            if (reflectAssembly.GetName().GetPublicKey().Length == 0)
181            {
182                //If the user did not allow the plug-in to load, don't load it.
183                if (ManagerLibrary.Instance.Settings.ApprovedPlugins.
184                    IndexOf(instance.AssemblyInfo.GUID) == -1)
185                {
186                    return;
187                }
188            }
189
190            //Load the plugin
191            instance.Assembly = Assembly.LoadFrom(filePath);
192
193            //Initialize the plugin
194            IPlugin pluginInterface = (IPlugin)Activator.CreateInstance(
195                instance.Assembly.GetType(typePlugin.ToString()));
196            pluginInterface.Initialize(this);
197            instance.Plugin = pluginInterface;
198
199            //And broadcast the plugin load event
200            OnPluginLoaded(instance);
201        }
202
203        Assembly AssemblyResolve(object sender, ResolveEventArgs args)
204        {
205            lock (plugins)
206                foreach (PluginInstance instance in plugins)
207                    if (instance.Assembly.FullName == args.Name)
208                        return instance.Assembly;
209            return null;
210        }
211
212        Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
213        {
214            return Assembly.ReflectionOnlyLoad(args.Name);
215        }
216
217        private List<PluginInstance> plugins = new List<PluginInstance>();
218    }
219
220    /// <summary>
221    /// Structure holding the instance values of the plugin like handle and path.
222    /// </summary>
223    public class PluginInstance
224    {
225        /// <summary>
226        /// Constructor
227        /// </summary>
228        /// <param name="assembly">The assembly representing this plugin.</param>
229        /// <param name="path">The path to the ass</param>
230        /// <param name="plugin"></param>
231        internal PluginInstance(Assembly assembly, IPlugin plugin)
232        {
233            Assembly = assembly;
234            Plugin = plugin;
235        }
236
237        /// <summary>
238        /// Gets the Assembly this plugin instance came from.
239        /// </summary>
240        public Assembly Assembly
241        {
242            get
243            {
244                return assembly;
245            }
246            internal set
247            {
248                assembly = value;
249
250                assemblyInfo.Version = assembly.GetName().Version;
251                IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(assembly);
252                foreach (CustomAttributeData attr in attributes)
253                    if (attr.Constructor.DeclaringType == typeof(GuidAttribute))
254                        assemblyInfo.GUID = new Guid((string)attr.ConstructorArguments[0].Value);
255                    else if (attr.Constructor.DeclaringType == typeof(AssemblyCompanyAttribute))
256                        assemblyInfo.Author = (string)attr.ConstructorArguments[0].Value;
257            }
258        }
259
260        /// <summary>
261        /// Gets the attributes of the assembly, loading from reflection-only sources.
262        /// </summary>
263        public AssemblyInfo AssemblyInfo
264        {
265            get
266            {
267                return assemblyInfo;
268            }
269            internal set
270            {
271                assemblyInfo = value;
272            }
273        }
274
275        /// <summary>
276        /// Gets the IPlugin interface which the plugin exposed.
277        /// </summary>
278        public IPlugin Plugin
279        {
280            get
281            {
282                return plugin;
283            }
284            internal set
285            {
286                plugin = value;
287            }
288        }
289
290        private Assembly assembly;
291        private AssemblyInfo assemblyInfo;
292        private IPlugin plugin;
293    }
294
295    /// <summary>
296    /// Reflection-only information retrieved from the assembly.
297    /// </summary>
298    public struct AssemblyInfo
299    {
300        /// <summary>
301        /// The GUID of the assembly.
302        /// </summary>
303        public Guid GUID;
304
305        /// <summary>
306        /// The publisher of the assembly.
307        /// </summary>
308        public string Author;
309
310        /// <summary>
311        /// The version of the assembly.
312        /// </summary>
313        public Version Version;
314    }
315
316    /// <summary>
317    /// Basic plugin interface which allows for the main program to utilize the
318    /// functions in the DLL
319    /// </summary>
320    public interface IPlugin : IDisposable
321    {
322        /// <summary>
323        /// Initializer.
324        /// </summary>
325        /// <param name="host">The host object which can be used for two-way
326        /// communication with the program.</param>
327        void Initialize(Host host);
328
329        /// <summary>
330        /// The name of the plug-in, used for descriptive purposes in the UI
331        /// </summary>
332        string Name
333        {
334            get;
335        }
336
337        /// <summary>
338        /// The author of the plug-in, used for display in the UI and for users
339        /// to contact the author about bugs. Must be in the format:
340        ///     (.+) \<([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)\.([a-zA-Z0-9]+)\>
341        /// </summary>
342        /// <example>Joel Low <joel@joelsplace.sg></example>
343        string Author
344        {
345            get;
346        }
347
348        /// <summary>
349        /// Determines whether the plug-in is configurable.
350        /// </summary>
351        bool Configurable
352        {
353            get;
354        }
355
356        /// <summary>
357        /// Fulfil a request to display the settings for this plug-in.
358        /// </summary>
359        /// <param name="parent">The parent control which the settings dialog should
360        /// be parented with.</param>
361        void DisplaySettings(Control parent);
362    }
363}
Note: See TracBrowser for help on using the repository browser.