source: trunk/eraser6/Eraser.Manager/Plugins.cs @ 1360

Revision 1360, 12.4 KB checked in by lowjoel, 5 years ago (diff)

Eraser's still under development, so update the copyright notice.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2009 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;
29using System.Security.Cryptography;
30using System.Security.Cryptography.X509Certificates;
31using Eraser.Util;
32
33namespace Eraser.Manager.Plugin
34{
35    /// <summary>
36    /// The plugins host interface which is used for communicating with the host
37    /// program.
38    /// </summary>
39    /// <remarks>Remember to call Load to load the plugins into memory, otherwise
40    /// they will never be loaded.</remarks>
41    public abstract class Host : IDisposable
42    {
43        #region IDisposable members
44        protected virtual void Dispose(bool disposing)
45        {
46        }
47
48        /// <summary>
49        /// Cleans up resources used by the host. Also unloads all loaded plugins.
50        /// </summary>
51        public void Dispose()
52        {
53            Dispose(true);
54            GC.SuppressFinalize(this);
55        }
56        #endregion
57
58        /// <summary>
59        /// Getter that retrieves the global plugin host instance.
60        /// </summary>
61        public static Host Instance
62        {
63            get { return ManagerLibrary.Instance.Host; }
64        }
65
66        /// <summary>
67        /// Retrieves the list of currently loaded plugins.
68        /// </summary>
69        /// <remarks>The returned list is read-only</remarks>
70        public abstract ICollection<PluginInstance> Plugins
71        {
72            get;
73        }
74
75        /// <summary>
76        /// Loads all plugins into memory.
77        /// </summary>
78        public abstract void Load();
79
80        /// <summary>
81        /// The plugin loaded event.
82        /// </summary>
83        public EventHandler<PluginLoadedEventArgs> PluginLoaded { get; set; }
84
85        /// <summary>
86        /// Event callback executor for the OnPluginLoad Event
87        /// </summary>
88        internal void OnPluginLoaded(object sender, PluginLoadedEventArgs e)
89        {
90            if (PluginLoaded != null)
91                PluginLoaded(sender, e);
92        }
93
94        /// <summary>
95        /// Loads a plugin.
96        /// </summary>
97        /// <param name="filePath">The absolute or relative file path to the
98        /// DLL.</param>
99        public abstract void LoadPlugin(string filePath);
100    }
101
102    /// <summary>
103    /// Event argument for the plugin loaded event.
104    /// </summary>
105    public class PluginLoadedEventArgs : EventArgs
106    {
107        /// <summary>
108        /// Constructor.
109        /// </summary>
110        /// <param name="instance">The plugin instance of the recently loaded plugin.</param>
111        public PluginLoadedEventArgs(PluginInstance instance)
112        {
113            Instance = instance;
114        }
115
116        /// <summary>
117        /// The <see cref="PluginInstance"/> object representing the newly loaded plugin.
118        /// </summary>
119        public PluginInstance Instance { get; private set; }
120    }
121
122    /// <summary>
123    /// The default plugins host implementation.
124    /// </summary>
125    internal class DefaultHost : Host
126    {
127        /// <summary>
128        /// Constructor. Loads all plugins in the Plugins folder.
129        /// </summary>
130        public DefaultHost()
131        {
132        }
133
134        public override void Load()
135        {
136            AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
137            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ResolveReflectionDependency;
138            string pluginsFolder = Path.Combine(
139                Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), //Assembly location
140                PLUGINSFOLDER //Plugins folder
141            );
142
143            foreach (string fileName in Directory.GetFiles(pluginsFolder))
144            {
145                FileInfo file = new FileInfo(fileName);
146                if (file.Extension.Equals(".dll"))
147                    try
148                    {
149                        LoadPlugin(file.FullName);
150                    }
151                    catch (BadImageFormatException)
152                    {
153                    }
154                    catch (FileLoadException)
155                    {
156                    }
157            }
158        }
159
160        protected override void Dispose(bool disposing)
161        {
162            if (disposing)
163            {
164                //Unload all the plugins. This will cause all the plugins to execute
165                //the cleanup code.
166                foreach (PluginInstance plugin in plugins)
167                    if (plugin.Plugin != null)
168                        plugin.Plugin.Dispose();
169            }
170        }
171
172        /// <summary>
173        /// The path to the folder containing the plugins.
174        /// </summary>
175        public const string PLUGINSFOLDER = "Plugins";
176
177        public override ICollection<PluginInstance> Plugins
178        {
179            get { return plugins.AsReadOnly(); }
180        }
181
182        public override void LoadPlugin(string filePath)
183        {
184            //Create the PluginInstance structure
185            Assembly reflectAssembly = Assembly.ReflectionOnlyLoadFrom(filePath);
186            PluginInstance instance = new PluginInstance(reflectAssembly, null);
187            Type typePlugin = null;
188
189            //Iterate over every exported type, checking if it implements IPlugin
190            foreach (Type type in instance.Assembly.GetExportedTypes())
191            {
192                //Check for an implementation of IPlugin
193                Type typeInterface = type.GetInterface("Eraser.Manager.Plugin.IPlugin", true);
194                if (typeInterface != null)
195                {
196                    typePlugin = type;
197                    break;
198                }
199            }
200
201            //If the typePlugin type is empty the assembly doesn't implement IPlugin; we
202            //aren't interested.
203            if (typePlugin == null)
204                return;
205
206            //OK this assembly is a plugin
207            lock (plugins)
208                plugins.Add(instance);
209
210            //The plugin was not disabled or it was loaded for the first time. Check
211            //the plugin for the presence of a valid signature.
212            IDictionary<Guid, bool> approvals = ManagerLibrary.Settings.PluginApprovals;
213            if ((reflectAssembly.GetName().GetPublicKey().Length == 0 ||
214                !MsCorEEApi.VerifyStrongName(filePath) ||
215                instance.AssemblyAuthenticode == null) &&
216                !approvals.ContainsKey(instance.AssemblyInfo.Guid))
217            {
218                return;
219            }
220
221            //Load the plugin
222            instance.Assembly = Assembly.LoadFrom(filePath);
223
224            //See if the plugin belongs to us (same signature) and if the assembly
225            //declares an IsCore attribute.
226            if (reflectAssembly.GetName().GetPublicKey().Length ==
227                Assembly.GetExecutingAssembly().GetName().GetPublicKey().Length)
228            {
229                bool sameKey = true;
230                byte[] reflectAssemblyKey = reflectAssembly.GetName().GetPublicKey();
231                byte[] thisAssemblyKey = Assembly.GetExecutingAssembly().GetName().GetPublicKey();
232                for (int i = 0, j = reflectAssemblyKey.Length; i != j; ++i)
233                    if (reflectAssemblyKey[i] != thisAssemblyKey[i])
234                    {
235                        sameKey = false;
236                        break;
237                    }
238
239                //Check for the IsCore attribute.
240                if (sameKey)
241                {
242                    object[] attr = instance.Assembly.GetCustomAttributes(typeof(CoreAttribute), true);
243                    if (attr.Length != 0)
244                        instance.IsCore = true;
245                }
246            }
247
248            //See if the user disabled this plugin (users cannot disable Core plugins)
249            if (approvals.ContainsKey(instance.AssemblyInfo.Guid) &&
250                !approvals[instance.AssemblyInfo.Guid] && !instance.IsCore)
251            {
252                return;
253            }
254
255            //Initialize the plugin
256            IPlugin pluginInterface = (IPlugin)Activator.CreateInstance(
257                instance.Assembly.GetType(typePlugin.ToString()));
258            pluginInterface.Initialize(this);
259            instance.Plugin = pluginInterface;
260
261            //And broadcast the plugin load event
262            OnPluginLoaded(this, new PluginLoadedEventArgs(instance));
263        }
264
265        private Assembly AssemblyResolve(object sender, ResolveEventArgs args)
266        {
267            lock (plugins)
268                foreach (PluginInstance instance in plugins)
269                    if (instance.Assembly.FullName == args.Name)
270                        return instance.Assembly;
271            return null;
272        }
273
274        private Assembly ResolveReflectionDependency(object sender, ResolveEventArgs args)
275        {
276            return Assembly.ReflectionOnlyLoad(args.Name);
277        }
278
279        private List<PluginInstance> plugins = new List<PluginInstance>();
280    }
281
282    /// <summary>
283    /// Structure holding the instance values of the plugin like handle and path.
284    /// </summary>
285    public class PluginInstance
286    {
287        /// <summary>
288        /// Constructor
289        /// </summary>
290        /// <param name="assembly">The assembly representing this plugin.</param>
291        /// <param name="path">The path to the ass</param>
292        /// <param name="plugin"></param>
293        internal PluginInstance(Assembly assembly, IPlugin plugin)
294        {
295            Assembly = assembly;
296            Plugin = plugin;
297            IsCore = false;
298
299            //Verify the certificate in the assembly.
300            if (WintrustApi.VerifyAuthenticode(assembly.Location))
301            {
302                X509Certificate2 cert = new X509Certificate2(
303                    X509Certificate.CreateFromSignedFile(assembly.Location));
304                AssemblyAuthenticode = cert;
305            }
306        }
307
308        /// <summary>
309        /// Gets the Assembly this plugin instance came from.
310        /// </summary>
311        public Assembly Assembly
312        {
313            get
314            {
315                return assembly;
316            }
317            internal set
318            {
319                assembly = value;
320
321                AssemblyInfo info = new AssemblyInfo();
322                info.Version = assembly.GetName().Version;
323                IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(assembly);
324                foreach (CustomAttributeData attr in attributes)
325                    if (attr.Constructor.DeclaringType == typeof(GuidAttribute))
326                        info.Guid = new Guid((string)attr.ConstructorArguments[0].Value);
327                    else if (attr.Constructor.DeclaringType == typeof(AssemblyCompanyAttribute))
328                        info.Author = (string)attr.ConstructorArguments[0].Value;
329
330                this.AssemblyInfo = info;
331            }
332        }
333
334        /// <summary>
335        /// Gets the attributes of the assembly, loading from reflection-only sources.
336        /// </summary>
337        public AssemblyInfo AssemblyInfo { get; private set; }
338
339        /// <summary>
340        /// The Authenticode signature used for signing the assembly.
341        /// </summary>
342        public X509Certificate2 AssemblyAuthenticode { get; private set; }
343
344        /// <summary>
345        /// Gets whether the plugin is required for the functioning of Eraser (and
346        /// therefore cannot be disabled.)
347        /// </summary>
348        public bool IsCore { get; internal set; }
349
350        /// <summary>
351        /// Gets the IPlugin interface which the plugin exposed.
352        /// </summary>
353        public IPlugin Plugin { get; internal set; }
354
355        private Assembly assembly;
356    }
357
358    /// <summary>
359    /// Reflection-only information retrieved from the assembly.
360    /// </summary>
361    public struct AssemblyInfo
362    {
363        /// <summary>
364        /// The GUID of the assembly.
365        /// </summary>
366        public Guid Guid { get; set; }
367
368        /// <summary>
369        /// The publisher of the assembly.
370        /// </summary>
371        public string Author { get; set; }
372
373        /// <summary>
374        /// The version of the assembly.
375        /// </summary>
376        public Version Version { get; set; }
377
378        public override bool Equals(object obj)
379        {
380            if (!(obj is AssemblyInfo))
381                return false;
382            return Equals((AssemblyInfo)obj);
383        }
384
385        public bool Equals(AssemblyInfo other)
386        {
387            return Guid == other.Guid;
388        }
389
390        public static bool operator ==(AssemblyInfo assembly1, AssemblyInfo assembly2)
391        {
392            return assembly1.Equals(assembly2);
393        }
394
395        public static bool operator !=(AssemblyInfo assembly1, AssemblyInfo assembly2)
396        {
397            return !assembly1.Equals(assembly2);
398        }
399
400        public override int GetHashCode()
401        {
402            return Guid.GetHashCode();
403        }
404    }
405
406    /// <summary>
407    /// Basic plugin interface which allows for the main program to utilize the
408    /// functions in the DLL
409    /// </summary>
410    public interface IPlugin : IDisposable
411    {
412        /// <summary>
413        /// Initializer.
414        /// </summary>
415        /// <param name="host">The host object which can be used for two-way
416        /// communication with the program.</param>
417        void Initialize(Host host);
418
419        /// <summary>
420        /// The name of the plug-in, used for descriptive purposes in the UI
421        /// </summary>
422        string Name
423        {
424            get;
425        }
426
427        /// <summary>
428        /// The author of the plug-in, used for display in the UI and for users
429        /// to contact the author about bugs. Must be in the format:
430        ///     (.+) \<([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)\.([a-zA-Z0-9]+)\>
431        /// </summary>
432        /// <example>Joel Low <joel@joelsplace.sg></example>
433        string Author
434        {
435            get;
436        }
437
438        /// <summary>
439        /// Determines whether the plug-in is configurable.
440        /// </summary>
441        bool Configurable
442        {
443            get;
444        }
445
446        /// <summary>
447        /// Fulfil a request to display the settings for this plug-in.
448        /// </summary>
449        /// <param name="parent">The parent control which the settings dialog should
450        /// be parented with.</param>
451        void DisplaySettings(Control parent);
452    }
453
454    /// <summary>
455    /// Declares that the entity referenced is a core plugin and cannot be unloaded.
456    /// Only plugins signed with the same signature as the Manager library will be
457    /// considered to be safe and therefore checked for this attribute.
458    /// </summary>
459    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
460    public sealed class CoreAttribute : Attribute
461    {
462    }
463}
Note: See TracBrowser for help on using the repository browser.