/
DesigntimeLicenseContext.cs
165 lines (146 loc) · 7.45 KB
/
DesigntimeLicenseContext.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
namespace System.ComponentModel.Design
{
/// <summary>
/// Provides design-time support for licensing.
/// </summary>
public class DesigntimeLicenseContext : LicenseContext
{
internal Hashtable _savedLicenseKeys = new Hashtable();
/// <summary>
/// Gets or sets the license usage mode.
/// </summary>
public override LicenseUsageMode UsageMode => LicenseUsageMode.Designtime;
/// <summary>
/// Gets a saved license key.
/// </summary>
public override string? GetSavedLicenseKey(Type type, Assembly? resourceAssembly) => null;
/// <summary>
/// Sets a saved license key.
/// </summary>
public override void SetSavedLicenseKey(Type type, string key)
{
ArgumentNullException.ThrowIfNull(type);
_savedLicenseKeys[type.AssemblyQualifiedName!] = key;
}
}
internal sealed class RuntimeLicenseContext : LicenseContext
{
internal Hashtable? _savedLicenseKeys;
/// <summary>
/// This method takes a file URL and converts it to a local path. The trick here is that
/// if there is a '#' in the path, everything after this is treated as a fragment. So
/// we need to append the fragment to the end of the path.
/// </summary>
private static string GetLocalPath(string fileName)
{
Debug.Assert(fileName != null && fileName.Length > 0, "Cannot get local path, fileName is not valid");
Uri uri = new Uri(fileName);
return uri.LocalPath + uri.Fragment;
}
[UnconditionalSuppressMessage("SingleFile", "IL3000: Avoid accessing Assembly file path when publishing as a single file",
Justification = "Suppressing the warning until gets fixed, see https://github.com/dotnet/runtime/issues/50821")]
public override string? GetSavedLicenseKey(Type type, Assembly? resourceAssembly)
{
if (_savedLicenseKeys == null || _savedLicenseKeys[type.AssemblyQualifiedName!] == null)
{
_savedLicenseKeys ??= new Hashtable();
resourceAssembly ??= Assembly.GetEntryAssembly();
if (resourceAssembly == null)
{
// If Assembly.EntryAssembly returns null, then we will
// try everything.
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
// Assemblies loaded in memory return empty string from Location.
string location = asm.Location;
if (location == string.Empty)
continue;
string fileName = new FileInfo(location).Name;
Stream? s = asm.GetManifestResourceStream(fileName + ".licenses");
// Since the casing may be different depending on how the assembly was loaded,
// we'll do a case insensitive lookup for this manifest resource stream...
s ??= CaseInsensitiveManifestResourceStreamLookup(asm, fileName + ".licenses");
if (s != null)
{
DesigntimeLicenseContextSerializer.Deserialize(s, fileName.ToUpperInvariant(), this);
break;
}
}
}
else
{
string location = resourceAssembly.Location;
if (location != string.Empty)
{
string fileName = Path.GetFileName(location);
string licResourceName = fileName + ".licenses";
// First try the filename
Stream? s = resourceAssembly.GetManifestResourceStream(licResourceName);
if (s == null)
{
string? resolvedName = null;
CompareInfo comparer = CultureInfo.InvariantCulture.CompareInfo;
string shortAssemblyName = resourceAssembly.GetName().Name!;
// If the assembly has been renamed, we try our best to find a good match in the available resources
// by looking at the assembly name (which doesn't change even after a file rename) + ".exe.licenses" or + ".dll.licenses"
foreach (string existingName in resourceAssembly.GetManifestResourceNames())
{
if (comparer.Compare(existingName, licResourceName, CompareOptions.IgnoreCase) == 0 ||
comparer.Compare(existingName, shortAssemblyName + ".exe.licenses", CompareOptions.IgnoreCase) == 0 ||
comparer.Compare(existingName, shortAssemblyName + ".dll.licenses", CompareOptions.IgnoreCase) == 0)
{
resolvedName = existingName;
break;
}
}
if (resolvedName != null)
{
s = resourceAssembly.GetManifestResourceStream(resolvedName);
}
}
if (s != null)
{
DesigntimeLicenseContextSerializer.Deserialize(s, fileName.ToUpperInvariant(), this);
}
}
}
}
return (string?)_savedLicenseKeys[type.AssemblyQualifiedName!];
}
/**
* Looks up a .licenses file in the assembly manifest using
* case-insensitive lookup rules. We do this because the name
* we are attempting to locate could have different casing
* depending on how the assembly was loaded.
**/
private static Stream? CaseInsensitiveManifestResourceStreamLookup(Assembly satellite, string name)
{
CompareInfo comparer = CultureInfo.InvariantCulture.CompareInfo;
// Loop through the resource names in the assembly.
// We try to handle the case where the assembly file name has been renamed
// by trying to guess the original file name based on the assembly name.
string assemblyShortName = satellite.GetName().Name!;
foreach (string existingName in satellite.GetManifestResourceNames())
{
if (comparer.Compare(existingName, name, CompareOptions.IgnoreCase) == 0 ||
comparer.Compare(existingName, assemblyShortName + ".exe.licenses") == 0 ||
comparer.Compare(existingName, assemblyShortName + ".dll.licenses") == 0)
{
name = existingName;
break;
}
}
// Finally, attempt to return our stream based on the
// case insensitive match we found.
return satellite.GetManifestResourceStream(name);
}
}
}