• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

luttje / Key2Joy / 6721644640

01 Nov 2023 03:26PM UTC coverage: 43.687% (-0.2%) from 43.884%
6721644640

push

github

luttje
add cheating warning

760 of 2407 branches covered (0.0%)

Branch coverage included in aggregate %.

3890 of 8237 relevant lines covered (47.23%)

12148.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.65
/Core/Key2Joy.PluginHost/PluginHost.cs
1
using System;
2
using System.AddIn.Contract;
3
using System.Collections.Generic;
4
using System.IO;
5
using System.Reflection;
6
using System.Security;
7
using System.Security.Cryptography;
8
using System.Security.Permissions;
9
using System.Security.Policy;
10
using System.Text;
11
using Key2Joy.Contracts;
12
using Key2Joy.Contracts.Plugins;
13
using Key2Joy.Contracts.Plugins.Remoting;
14

15
namespace Key2Joy.PluginHost;
16

17
public class PluginHost : MarshalByRefObject, IPluginHost
18
{
19
    public event RemoteEventHandlerCallback AnyEvent;
20

21
    private PluginBase loadedPlugin;
22
    private AppDomain sandboxDomain;
23
    private string pluginAssemblyPath;
24
    private string pluginAssemblyName;
25

26
    /// <summary>
27
    /// This method can't just return the PluginBase, since it's created in an AppDomain in the PluginHost process. Passing it
28
    /// upwards to the main app would not work, since it has no remote connection to it. Therefor we store the plugin and let
29
    /// the main app call functions on this loader to interact with it.
30
    /// </summary>
31
    /// <param name="assemblyPath"></param>
32
    /// <param name="assemblyName"></param>
33
    /// <param name="loadedChecksum">The checksum after the plugin was loaded</param>
34
    /// <param name="expectedChecksum">The checksum the plugin must have to be loaded</param>
35
    /// <exception cref="ArgumentNullException"></exception>
36
    /// <exception cref="ApplicationException"></exception>
37
    /// <exception cref="PluginLoadException">Throws when the plugin failed to load</exception>
38
    public void LoadPlugin(string assemblyPath, string assemblyName, out string loadedChecksum, string expectedChecksum = null)
39
    {
40
        if (string.IsNullOrEmpty(assemblyPath))
14!
41
        {
42
            throw new ArgumentNullException("assemblyPath");
×
43
        }
44

45
        if (string.IsNullOrEmpty(assemblyName))
14!
46
        {
47
            throw new ArgumentNullException("assembly");
×
48
        }
49

50
        var pluginTypeName = $"{assemblyName}.Plugin";
14✔
51

52
        this.pluginAssemblyPath = assemblyPath;
14✔
53
        this.pluginAssemblyName = assemblyName;
14✔
54

55
        Console.WriteLine("Loading plugin {0},{1}", assemblyName, pluginTypeName);
14✔
56

57
        // Let plugins specify additional permissions
58
        var permissionsXml = GetAdditionalPermissionsXml(this.pluginAssemblyPath);
14✔
59
        loadedChecksum = CalculatePermissionsChecksum(permissionsXml);
14✔
60

61
        if (expectedChecksum != null && loadedChecksum != expectedChecksum)
14✔
62
        {
63
            throw new PluginLoadException($"Plugin permissions checksum mismatch! \n\nExpected '{expectedChecksum}',  got '{loadedChecksum}'.\n\nThis plugin will be disabled for you. If you trust it you can re-enable it later.");
1✔
64
        }
65

66
        var pluginDirectory = Path.GetDirectoryName(assemblyPath);
13✔
67

68
        var pluginDataDirectory = Path.Combine(pluginDirectory, "data");
13✔
69
        Directory.CreateDirectory(pluginDataDirectory);
13✔
70

71
        AppDomainSetup sandboxDomainSetup = new()
13✔
72
        {
13✔
73
            ApplicationBase = pluginDirectory,
13✔
74
            PrivateBinPath = pluginDirectory,
13✔
75
        };
13✔
76

77
        Evidence evidence = new();
13✔
78
        evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
13✔
79

80
        var permissions = SecurityManager.GetStandardSandbox(evidence);
13✔
81
        var isUnrestricted = false;
13✔
82

83
        if (permissionsXml != null)
13✔
84
        {
85
            PermissionSet additionalPermissions = new(PermissionState.None);
13✔
86
            additionalPermissions.FromXml(SecurityElement.FromString(permissionsXml));
13✔
87

88
            if (additionalPermissions.Count > 0)
13✔
89
            {
90
                var securityOverride = additionalPermissions.GetPermission(typeof(SecurityOverride));
13✔
91

92
                if (securityOverride != null)
13!
93
                {
94
                    isUnrestricted = ((SecurityOverride)securityOverride).IsUnrestricted;
×
95
                }
96
                else
97
                {
98
                    var intersection = additionalPermissions.Intersect(
13✔
99
                        AllowedPermissionsWithDescriptions
13✔
100
                        .GetAllowedPermissionsWithDescriptions()
13✔
101
                        .AllowedPermissions
13✔
102
                    );
13✔
103

104
                    if (intersection == null || !intersection.Equals(additionalPermissions))
13!
105
                    {
106
                        throw new PluginLoadException($"Some plugin permissions are not allowed: {additionalPermissions}");
×
107
                    }
108

109
                    permissions = permissions.Union(additionalPermissions);
13✔
110
                }
111
            }
112
        }
113

114
        if (isUnrestricted)
13!
115
        {
116
            permissions = new PermissionSet(PermissionState.Unrestricted);
×
117
        }
118
        else
119
        {
120
            // Required to instantiate Controls inside the plugin
121
            permissions.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, pluginDirectory));
13✔
122
            permissions.AddPermission(new FileIOPermission(FileIOPermissionAccess.PathDiscovery, pluginDirectory));
13✔
123
            permissions.AddPermission(new UIPermission(UIPermissionWindow.AllWindows));
13✔
124
            permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.MemberAccess));
13✔
125
            // Needed to serialize objects back to the host app
126
            permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter));
13✔
127

128
            permissions.AddPermission(new FileIOPermission(FileIOPermissionAccess.AllAccess, pluginDataDirectory));
13✔
129
        }
130

131
        this.sandboxDomain = AppDomain.CreateDomain("Sandbox", evidence, sandboxDomainSetup, permissions);
13✔
132

133
        var pluginAssemblyResolver = (PluginAssemblyResolver)this.sandboxDomain.CreateInstanceAndUnwrap(
13✔
134
            typeof(PluginAssemblyResolver).Assembly.FullName,
13✔
135
            typeof(PluginAssemblyResolver).FullName);
13✔
136
        pluginAssemblyResolver.SetPluginDirectory(pluginDirectory);
13✔
137

138
        this.sandboxDomain.AssemblyResolve += pluginAssemblyResolver.ResolveAssembly;
13✔
139

140
        this.loadedPlugin = (PluginBase)this.sandboxDomain.CreateInstanceAndUnwrap(assemblyName, pluginTypeName);
13✔
141
        this.loadedPlugin.PluginDirectory = pluginDirectory;
13✔
142
        this.loadedPlugin.PluginDataDirectory = pluginDataDirectory;
13✔
143
        this.loadedPlugin.Initialize();
13✔
144
    }
13✔
145

146
    public string GetPluginName() => this.loadedPlugin.Name;
×
147

148
    public string GetPluginAuthor() => this.loadedPlugin.Author;
×
149

150
    public string GetPluginWebsite() => this.loadedPlugin.Website;
×
151

152
    public static string GetAdditionalPermissionsXml(string pluginAssemblyPath)
153
    {
154
        var pluginDirectoryPath = Path.GetDirectoryName(pluginAssemblyPath);
16✔
155
        var permissionsFilePath = Path.Combine(pluginDirectoryPath, "permissions.xml");
16✔
156

157
        if (File.Exists(permissionsFilePath))
16!
158
        {
159
            var permissionsFile = File.ReadAllText(permissionsFilePath);
16✔
160

161
            return permissionsFile;
16✔
162
        }
163

164
        return null;
×
165
    }
166

167
    public static string CalculatePermissionsChecksum(string permissionsXml)
168
    {
169
        var hash = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(permissionsXml ?? string.Empty));
16!
170
        StringBuilder builder = new();
16✔
171
        for (var i = 0; i < hash.Length; i++)
544✔
172
        {
173
            builder.Append(hash[i].ToString("X2"));
256✔
174
        }
175
        return builder.ToString();
16✔
176
    }
177

178
    public PluginActionInsulator CreateAction(string fullTypeName, object[] constructorArguments)
179
    {
180
        var pluginAction = (PluginAction)this.sandboxDomain.CreateInstanceFromAndUnwrap(
12✔
181
            this.pluginAssemblyPath,
12✔
182
            fullTypeName,
12✔
183
            false,
12✔
184
            BindingFlags.Default,
12✔
185
            null,
12✔
186
            constructorArguments,
12✔
187
            null,
12✔
188
            null
12✔
189
        );
12✔
190
        pluginAction.Plugin = this.loadedPlugin;
12✔
191
        return new PluginActionInsulator(pluginAction);
12✔
192
    }
193

194
    public PluginTriggerInsulator CreateTrigger(string fullTypeName, object[] constructorArguments)
195
    {
196
        var pluginTrigger = (PluginTrigger)this.sandboxDomain.CreateInstanceFromAndUnwrap(
×
197
            this.pluginAssemblyPath,
×
198
            fullTypeName,
×
199
            false,
×
200
            BindingFlags.Default,
×
201
            null,
×
202
            constructorArguments,
×
203
            null,
×
204
            null
×
205
        );
×
206
        pluginTrigger.Plugin = this.loadedPlugin;
×
207
        return new PluginTriggerInsulator(pluginTrigger);
×
208
    }
209

210
    public void Test_AnyEvent(object sender, RemoteEventArgs e) => AnyEvent?.Invoke(sender, e);
×
211

212
    public INativeHandleContract CreateFrameworkElementContract(string controlTypeName, SubscriptionTicket[] eventSubscriptions = null)
213
    {
214
        Dictionary<string, RemoteEventHandler> eventHandlers = new();
×
215

216
        if (eventSubscriptions != null)
×
217
        {
218
            foreach (var subscription in eventSubscriptions)
×
219
            {
220
                eventHandlers.Add(subscription.EventName, new RemoteEventHandler(subscription, this.Test_AnyEvent));
×
221
            }
222
        }
223

224
        var contract = (INativeHandleContract)Program.AppDispatcher.Invoke(CreateOnUiThread, this.pluginAssemblyName, controlTypeName, this.sandboxDomain, eventHandlers);
×
225
        return contract;
×
226
    }
227

228
    private static NativeHandleContractInsulator CreateOnUiThread(string assembly, string typeName, AppDomain appDomain, Dictionary<string, RemoteEventHandler> eventHandlers)
229
    {
230
        try
231
        {
232
            var controlHandle = appDomain.CreateInstance(assembly, typeName) ?? throw new InvalidOperationException("appDomain.CreateInstance() returned null for " + assembly + "," + typeName);
×
233
            var converterHandle = appDomain.CreateInstanceAndUnwrap(
×
234
                typeof(ViewContractConverter).Assembly.FullName,
×
235
                typeof(ViewContractConverter).FullName) as ViewContractConverter ?? throw new InvalidOperationException("appDomain.CreateInstance() returned null for ViewContractConverter");
×
236
            var contract = converterHandle.ConvertToContract(controlHandle, eventHandlers);
×
237
            NativeHandleContractInsulator insulator = new(contract, converterHandle);
×
238

239
            return insulator;
×
240
        }
241
        catch (Exception ex)
×
242
        {
243
            var message = string.Format("Error loading type '{0}' from assembly '{1}'. {2}",
×
244
                assembly, typeName, ex.Message);
×
245

246
            Output.WriteLine(message);
×
247
            throw new ApplicationException(message, ex);
×
248
        }
249
    }
×
250

251
    public void Terminate() => Environment.Exit(0);
14✔
252
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc