1 using System;
2 using System.CodeDom;
3 using System.CodeDom.Compiler;
4 using System.Collections.Generic;
5 using System.Configuration;
6 using System.Security.Permissions;
7 using System.Text;
8 using System.Web.Configuration;
9 using System.Web.Hosting;
10 using System.Web.Util;
11 using Microsoft.Build.Utilities;
12 using Microsoft.CSharp;
13 using Microsoft.VisualBasic;
14 using Microsoft.Win32;
15 
16 using FrameworkName=System.Runtime.Versioning.FrameworkName;
17 
18 namespace System.Web.Compilation {
19     internal class MultiTargetingUtil {
20 
21         // Well-known previous versions
22         static internal readonly FrameworkName FrameworkNameV20 = CreateFrameworkName(".NETFramework,Version=v2.0");
23         static internal readonly FrameworkName FrameworkNameV30 = CreateFrameworkName(".NETFramework,Version=v3.0");
24         static internal readonly FrameworkName FrameworkNameV35 = CreateFrameworkName(".NETFramework,Version=v3.5");
25         static internal readonly FrameworkName FrameworkNameV40 = CreateFrameworkName(".NETFramework,Version=v4.0");
26         static internal readonly FrameworkName FrameworkNameV45 = CreateFrameworkName(".NETFramework,Version=v4.5");
27 
28         internal static Version Version40 = new Version(4, 0);
29         internal static Version Version35 = new Version(3, 5);
30         private static FrameworkName s_targetFrameworkName = null;
31         private static string s_configTargetFrameworkMoniker = null;
32         private static object s_configTargetFrameworkMonikerLock = new object();
33         private static bool s_initializedConfigTargetFrameworkMoniker = false;
34         private static object s_targetFrameworkNameLock = new object();
35         private static string s_configTargetFrameworkAttributeName = "targetFramework";
36 
37         /// <summary>
38         /// Latest framework version.
39         /// </summary>
40         private static FrameworkName s_latestFrameworkName = null;
41 
42         private static List<FrameworkName> s_knownFrameworkNames = null;
43 
44         /// <summary>
45         /// Returns the target framework moniker, eg ".NETFramework,Version=3.5"
46         /// </summary>
47         internal static FrameworkName TargetFrameworkName {
48             get {
49                 EnsureFrameworkNamesInitialized();
50                 return s_targetFrameworkName;
51             }
52             set {
53                 s_targetFrameworkName = value;
54             }
55         }
56 
57         /// <summary>
58         /// Returns the current latest known framework moniker, eg ".NETFramework,Version=4.0"
59         /// </summary>
60         internal static FrameworkName LatestFrameworkName {
61             get {
62                 EnsureFrameworkNamesInitialized();
63                 return s_latestFrameworkName;
64             }
65         }
66 
67         internal static List<FrameworkName> KnownFrameworkNames {
68             get {
69                 EnsureFrameworkNamesInitialized();
70                 return s_knownFrameworkNames;
71             }
72         }
73 
EnsureFrameworkNamesInitialized()74         internal static void EnsureFrameworkNamesInitialized() {
75             if (s_targetFrameworkName == null) {
76                 lock (s_targetFrameworkNameLock) {
77                     if (s_targetFrameworkName == null) {
78                         InitializeKnownAndLatestFrameworkNames();
79                         InitializeTargetFrameworkName();
80                         Debug.Assert(s_targetFrameworkName != null, "s_targetFrameworkName should not be null");
81                     }
82                 }
83             }
84         }
85 
86         /// <summary>
87         /// Finds out what the known framework names and also the latest one
88         /// </summary>
InitializeKnownAndLatestFrameworkNames()89         private static void InitializeKnownAndLatestFrameworkNames() {
90             IList<string> names = ToolLocationHelper.GetSupportedTargetFrameworks();
91             Version latestVersion = null;
92             s_knownFrameworkNames = new List<FrameworkName>();
93             foreach (string name in names) {
94                 FrameworkName frameworkName = new FrameworkName(name);
95                 s_knownFrameworkNames.Add(frameworkName);
96                 Version version = GetFrameworkNameVersion(frameworkName);
97                 if (s_latestFrameworkName == null || latestVersion < version) {
98                     s_latestFrameworkName = frameworkName;
99                     latestVersion = version;
100                 }
101             }
102         }
103 
104         /// <summary>
105         /// Returns the string for the target framework as specified in the
106         /// config.
107         /// </summary>
108         internal static string ConfigTargetFrameworkMoniker {
109             get {
110                 if (!s_initializedConfigTargetFrameworkMoniker) {
111                     lock (s_configTargetFrameworkMonikerLock) {
112                         if (!s_initializedConfigTargetFrameworkMoniker) {
113                             RuntimeConfig appConfig = RuntimeConfig.GetAppConfig();
114                             CompilationSection compConfig = appConfig.Compilation;
115 
116                             string targetFramework = compConfig.TargetFramework;
117                             if (targetFramework != null) {
118                                 targetFramework = targetFramework.Trim();
119                             }
120 
121                             s_configTargetFrameworkMoniker = targetFramework;
122                             s_initializedConfigTargetFrameworkMoniker = true;
123                         }
124                     }
125                 }
126                 return s_configTargetFrameworkMoniker;
127             }
128         }
129 
130         /// <summary>
131         /// Checks what is the target framework version and initializes the targetFrameworkName
132         /// </summary>
InitializeTargetFrameworkName()133         private static void InitializeTargetFrameworkName() {
134             string targetFrameworkMoniker = ConfigTargetFrameworkMoniker;
135 
136             // Check if web.config exists, and if not, assume 4.0
137             if (!WebConfigExists) {
138                 s_targetFrameworkName = FrameworkNameV40;
139                 ValidateCompilerVersionFor40AndAbove();
140             }
141             else if (targetFrameworkMoniker == null) {
142                 if (BuildManagerHost.SupportsMultiTargeting) {
143                     // We check for null because the user could have specified
144                     // an empty string.
145                     // TargetFrameworkMoniker was not specified in config,
146                     // so we need to check codedom settings.
147                     InitializeTargetFrameworkNameFor20Or35();
148                 } else {
149                     // We are running in a 4.0 application pool or in the aspnet_compiler,
150                     // but the target framework moniker is not specified.
151                     // Assume it is 4.0 so that the application can run.
152                     s_targetFrameworkName = FrameworkNameV40;
153                 }
154             } else {
155                 // The targetFrameworkMonike is specified, so we need to validate it.
156                 InitializeTargetFrameworkNameFor40AndAbove(targetFrameworkMoniker);
157             }
158         }
159 
160         /// <summary>
161         /// Verifies that the moniker is valid, and that the version is 4.0 and above.
162         /// </summary>
ValidateTargetFrameworkMoniker(string targetFrameworkMoniker)163         private static void ValidateTargetFrameworkMoniker(string targetFrameworkMoniker) {
164             CompilationSection compConfig = RuntimeConfig.GetAppConfig().Compilation;
165             int lineNumber = compConfig.ElementInformation.LineNumber;
166             string source = compConfig.ElementInformation.Source;
167             try {
168                 string moniker = targetFrameworkMoniker;
169                 // Try treating it as a version, eg "4.0" first.
170                 Version v = GetVersion(targetFrameworkMoniker);
171                 if (v != null) {
172                     // If it is of the form "4.0", construct the full moniker string,
173                     // eg ".NETFramework,Version=v4.0"
174                     moniker = ".NETFramework,Version=v" + moniker;
175                 }
176                 s_targetFrameworkName = CreateFrameworkName(moniker);
177             }
178             catch (ArgumentException e) {
179                 throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_target_framework_version,
180                     s_configTargetFrameworkAttributeName, targetFrameworkMoniker, e.Message), source, lineNumber);
181             }
182             Version ver = GetFrameworkNameVersion(s_targetFrameworkName);
183             if (ver < Version40) {
184                 throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_lower_target_version, s_configTargetFrameworkAttributeName),
185                     source, lineNumber);
186             }
187 
188             // Check the specified version is no higher than the latest known framework for which we have
189             // reference assemblies installed.
190             Version latestVersion = GetFrameworkNameVersion(LatestFrameworkName);
191             if (latestVersion != null && latestVersion >= ver) {
192                 // If the specified version is lower than the latest version installed,
193                 // we are fine.
194                 return;
195             }
196 
197             // NOTE: This check is not entirely correct. See comments in GetInstalledTargetVersion().
198             // It might be possible that the actual installed (runtime) version is of a higher version,
199             // but the reference assemblies are not installed, so latestFrameworkName might be lower.
200             // In that case we also need to check the registry key.
201             int majorVersion = ver.Major;
202             Version installedTargetVersion = GetInstalledTargetVersion(majorVersion);
203             if (installedTargetVersion != null && installedTargetVersion >= ver) {
204                 return;
205             }
206 
207             if (IsSupportedVersion(s_targetFrameworkName)) {
208                 return;
209             }
210 
211             // If the above checks failed, report that the version is invalid, higher than expected
212             throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_higher_target_version, s_configTargetFrameworkAttributeName), source, lineNumber);
213         }
214 
215         [RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
GetInstalledTargetVersion(int majorVersion)216         private static Version GetInstalledTargetVersion(int majorVersion) {
217             // NOTE: This code is wrong to assume "Full", but it is left as is to avoid
218             // introducing any breaking change. The mitigation is handled by IsSupportedVersion which
219             // is more flexible with regards to framework profile.
220 
221             // registry key is of the form:
222             // [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full]
223             // "TargetVersion"="4.0.0"
224             // The path includes the major version, eg "v4" or "v5", so we need to use a parameter.
225             string path = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v" + majorVersion + @"\Full";
226             try {
227                 object o = Registry.GetValue(path, "TargetVersion", null);
228                 string targetVersion = o as string;
229                 if (!string.IsNullOrEmpty(targetVersion)) {
230                     Version ver = new Version(targetVersion);
231                     return ver;
232                 }
233             }
234             catch { // ignore exceptions
235             }
236             return null;
237         }
238 
239         [RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
IsSupportedVersion(FrameworkName frameworkName)240         private static bool IsSupportedVersion(FrameworkName frameworkName) {
241             // Look under the following registry to get the list of supported keys, and check for matching
242             // identifier and version.
243             // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319\SKUs\[TFM]
244             try {
245                 var name = new FrameworkName(frameworkName.Identifier, frameworkName.Version);
246                 var runtime = Environment.Version;
247                 var runtimeVersion = runtime.Major + "." + runtime.Minor + "." + runtime.Build;
248                 string path = @"SOFTWARE\Microsoft\.NETFramework\v" + runtimeVersion + @"\SKUs";
249                 var baseKey = Registry.LocalMachine.OpenSubKey(path);
250                 foreach (string subKey in baseKey.GetSubKeyNames()) {
251                     try {
252                         var subKeyName = CreateFrameworkName(subKey);
253                         var supportedName = new FrameworkName(subKeyName.Identifier, subKeyName.Version);
254                         if (String.Equals(name.FullName, supportedName.FullName, StringComparison.OrdinalIgnoreCase)) {
255                             return true;
256                         }
257                     }
258                     catch {
259                         continue;
260                     }
261                 }
262             }
263             catch {
264             }
265             return false;
266         }
267 
268         /// <summary>
269         /// Checks whether the application web.config exists or not
270         /// </summary>
271         private static bool WebConfigExists {
272             get {
273                 VirtualPath vpath = HttpRuntime.AppDomainAppVirtualPathObject;
274                 if (vpath != null) {
275                     string path = vpath.SimpleCombine(HttpConfigurationSystem.WebConfigFileName).MapPath();
276                     return System.IO.File.Exists(path);
277                 }
278                 return false;
279             }
280         }
281 
282         /// <summary>
283         /// Returns the higher compilerVersion specified in codedom for the case when targeting 2.0/3.5.
284         /// Either "v3.5" is returned, or "v2.0" is returned if the compilerVersion
285         /// is anything other that "v3.5". This is because the root web.config has compilerVersion=v4.0. If we
286         /// know that we are compiling for 2.0 or 3.5, then we override the value to 2.0 if it is not 3.5.
287         /// </summary>
GetCompilerVersionFor20Or35()288         private static string GetCompilerVersionFor20Or35() {
289             string vbCompilerVersion = GetCSharpCompilerVersion();
290             string csharpCompilerVersion = GetVisualBasicCompilerVersion();
291 
292             // The root web.config will have compilerVersion=4.0, so if we are targeting 2.0 or 3.5, we need to
293             // use compilerVersion=2.0 if the compilerVersion is NOT 3.5.
294             vbCompilerVersion = ReplaceCompilerVersionFor20Or35(vbCompilerVersion);
295             csharpCompilerVersion = ReplaceCompilerVersionFor20Or35(csharpCompilerVersion);
296 
297             Version vbVersion = CompilationUtil.GetVersionFromVString(vbCompilerVersion);
298             Version csVersion = CompilationUtil.GetVersionFromVString(csharpCompilerVersion);
299 
300             // Return the larger value as the intended version
301             if (vbVersion > csVersion) {
302                 return vbCompilerVersion;
303             }
304             return csharpCompilerVersion;
305         }
306 
307         /// <summary>
308         /// Checks codedom settings to determine whether we are targeting 2.0 or 3.5.
309         /// </summary>
InitializeTargetFrameworkNameFor20Or35()310         private static void InitializeTargetFrameworkNameFor20Or35() {
311             string compilerVersion = GetCompilerVersionFor20Or35();
312 
313             // Make sure the compiler version is either 2.0 or 3.5
314             if (CompilationUtil.IsCompilerVersion35(compilerVersion)) {
315                 s_targetFrameworkName = FrameworkNameV35;
316             }
317             else if (compilerVersion == "v2.0" || compilerVersion == null) {
318                 // If the compiler version is null, it means the user did not set it
319                 // in the codedom section.
320                 // We use 3.0 because it is not possible to distinguish between 2.0 and 3.0
321                 // by just looking at web.config.
322                 s_targetFrameworkName = FrameworkNameV30;
323             }
324             else {
325                 throw new ConfigurationErrorsException(SR.GetString(SR.Compiler_version_20_35_required, s_configTargetFrameworkAttributeName));
326             }
327         }
328 
329         /// <summary>
330         /// If the compilerVersion is anything other than "v3.5", return "v2.0".
331         /// </summary>
ReplaceCompilerVersionFor20Or35(string compilerVersion)332         private static string ReplaceCompilerVersionFor20Or35(string compilerVersion) {
333             if (CompilationUtil.IsCompilerVersion35(compilerVersion)) {
334                 return compilerVersion;
335             }
336             return "v2.0";
337         }
338 
GetCSharpCompilerVersion()339         private static string GetCSharpCompilerVersion() {
340             return CompilationUtil.GetCompilerVersion(typeof(CSharpCodeProvider));
341         }
342 
GetVisualBasicCompilerVersion()343         private static string GetVisualBasicCompilerVersion() {
344             return CompilationUtil.GetCompilerVersion(typeof(VBCodeProvider));
345         }
346 
ReportInvalidCompilerVersion(string compilerVersion)347         private static void ReportInvalidCompilerVersion(string compilerVersion) {
348             throw new ConfigurationErrorsException(SR.GetString(SR.Invalid_attribute_value, compilerVersion, CompilationUtil.CodeDomProviderOptionPath + "CompilerVersion"));
349         }
350 
InitializeTargetFrameworkNameFor40AndAbove(string targetFrameworkMoniker)351         private static void InitializeTargetFrameworkNameFor40AndAbove(string targetFrameworkMoniker) {
352             ValidateTargetFrameworkMoniker(targetFrameworkMoniker);
353             ValidateCompilerVersionFor40AndAbove();
354         }
355 
356         /// <summary>
357         /// Ensures that the compiler version is 4.0 and above.
358         /// </summary>
ValidateCompilerVersionFor40AndAbove()359         private static void ValidateCompilerVersionFor40AndAbove() {
360             // Since the root web.config already specifies 4.0, we need to make sure both compilerVersions
361             // are actually greater than or equal to 4.0, in case the user only sets compilerVersion=3.5
362             // for one language. (Dev10 bug 738202)
363             ValidateCompilerVersionFor40AndAbove(GetCSharpCompilerVersion());
364             ValidateCompilerVersionFor40AndAbove(GetVisualBasicCompilerVersion());
365         }
366 
ValidateCompilerVersionFor40AndAbove(string compilerVersion)367         private static void ValidateCompilerVersionFor40AndAbove(string compilerVersion) {
368             if (compilerVersion != null) {
369                 Exception exception = null;
370                 if (compilerVersion.Length < 4 || compilerVersion[0] != 'v') {
371                     ReportInvalidCompilerVersion(compilerVersion);
372                 }
373                 try {
374                     Version version = CompilationUtil.GetVersionFromVString(compilerVersion);
375                     if (version < Version40) {
376                         throw new ConfigurationErrorsException(SR.GetString(SR.Compiler_version_40_required, s_configTargetFrameworkAttributeName));
377                     }
378                 }
379                 catch (ArgumentNullException e) {
380                     exception = e;
381                 }
382                 catch (ArgumentOutOfRangeException e) {
383                     exception = e;
384                 }
385                 catch (ArgumentException e) {
386                     exception = e;
387                 }
388                 catch (FormatException e) {
389                     exception = e;
390                 }
391                 catch (OverflowException e) {
392                     exception = e;
393                 }
394                 if (exception != null) {
395                     ReportInvalidCompilerVersion(compilerVersion);
396                 }
397             }
398         }
399 
400         /// <summary>
401         /// Returns true if the target framework version is 3.5.
402         /// </summary>
403         internal static bool IsTargetFramework35 {
404             get {
405                 return Object.Equals(TargetFrameworkName, FrameworkNameV35);
406             }
407         }
408 
409         /// <summary>
410         /// Returns true if the target framework version is 2.0 or 3.0.
411         /// </summary>
412         internal static bool IsTargetFramework20 {
413             get {
414                 return Object.Equals(TargetFrameworkName, FrameworkNameV20) ||
415                     Object.Equals(TargetFrameworkName, FrameworkNameV30);
416             }
417         }
418 
419         // Gets the target framework version as a Version instance.
420         internal static Version TargetFrameworkVersion {
421             get {
422                 return GetFrameworkNameVersion(TargetFrameworkName);
423             }
424         }
425 
426         internal static bool IsTargetFramework40OrAbove {
427             get {
428                 return MultiTargetingUtil.TargetFrameworkVersion.Major >= 4;
429             }
430         }
431 
432         internal static bool IsTargetFramework45OrAbove {
433             get {
434                 return IsTargetFramework40OrAbove && TargetFrameworkVersion.Minor >= 5;
435             }
436         }
437 
438         /// <summary>
439         /// Enable use of RAR only in CBM scenarios
440         /// </summary>
441         internal static bool EnableReferenceAssemblyResolution {
442             get {
443                 return BuildManagerHost.InClientBuildManager; // Enable only in CBM scenarios.
444             }
445         }
446 
CreateFrameworkName(string name)447         internal static FrameworkName CreateFrameworkName(string name) {
448             return new FrameworkName(name);
449         }
450 
GetFrameworkNameVersion(FrameworkName name)451         private static Version GetFrameworkNameVersion(FrameworkName name) {
452             if (name == null) {
453                 return null;
454             }
455             return name.Version;
456         }
457 
458         /// <summary>
459         /// Returns a Version instance if possible from the version string.
460         /// Otherwise returns null.
461         /// </summary>
GetVersion(string version)462         private static Version GetVersion(string version) {
463             if (string.IsNullOrEmpty(version) || !char.IsDigit(version[0])) {
464                 return null;
465             }
466 
467             try {
468                 Version ver = new Version(version);
469                 return ver;
470             }
471             catch { }
472             return null;
473         }
474     }
475 }
476