source: trunk/eraser/Eraser.Manager/Plugins.cs @ 2010

Revision 2010, 14.3 KB checked in by lowjoel, 5 years ago (diff)

Fixed regression in r1891, the loading policies of plugins must be stored in the PluginInstance? structure for reference by plugin clients.

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