|
//------------------------------------------------------------------------------
// <copyright file="ClientConfigPaths.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Configuration {
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Cryptography;
using System.Security.Policy;
using System.Security.Permissions;
using System.Text;
using System.Globalization;
using Microsoft.Win32;
using Diagnostics.CodeAnalysis;
class ClientConfigPaths {
internal const string UserConfigFilename = "user.config";
const string ClickOnceDataDirectory = "DataDirectory";
const string ConfigExtension = ".config";
const int MAX_PATH = 260;
const int MAX_UNICODESTRING_LEN = short.MaxValue;
const int ERROR_INSUFFICIENT_BUFFER = 122; //https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
const int MAX_LENGTH_TO_USE = 25;
const string FILE_URI_LOCAL = "file:///";
const string FILE_URI_UNC = "file://";
const string FILE_URI = "file:";
const string HTTP_URI = "http://";
const string StrongNameDesc = "StrongName";
const string UrlDesc = "Url";
const string PathDesc = "Path";
static Char[] s_Base32Char = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '0', '1', '2', '3', '4', '5'};
static volatile ClientConfigPaths s_current;
static volatile bool s_currentIncludesUserConfig;
static volatile SecurityPermission s_serializationPerm;
static volatile SecurityPermission s_controlEvidencePerm;
bool _hasEntryAssembly;
bool _includesUserConfig;
string _applicationUri;
string _applicationConfigUri;
string _roamingConfigDirectory;
string _roamingConfigFilename;
string _localConfigDirectory;
string _localConfigFilename;
string _companyName;
string _productName;
string _productVersion;
[FileIOPermission(SecurityAction.Assert, AllFiles=FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read)]
[SecurityPermission(SecurityAction.Assert, UnmanagedCode=true)]
private ClientConfigPaths(string exePath, bool includeUserConfig) {
_includesUserConfig = includeUserConfig;
Assembly exeAssembly = null;
string applicationUri = null;
string applicationFilename = null;
// get the assembly and applicationUri for the file
if (exePath == null) {
// First check if a configuration file has been set for this app domain. If so, we will use that.
// The CLR would already have normalized this, so no further processing necessary.
AppDomain domain = AppDomain.CurrentDomain;
AppDomainSetup setup = domain.SetupInformation;
_applicationConfigUri = setup.ConfigurationFile;
// Now figure out the application path.
exeAssembly = Assembly.GetEntryAssembly();
if (exeAssembly != null) {
_hasEntryAssembly = true;
applicationUri = exeAssembly.CodeBase;
bool isFile = false;
// If it is a local file URI, convert it to its filename, without invoking Uri class.
// example: "file:///C:/WINNT/Microsoft.NET/Framework/v2.0.x86fre/csc.exe"
if (StringUtil.StartsWithIgnoreCase(applicationUri, FILE_URI_LOCAL)) {
isFile = true;
applicationUri = applicationUri.Substring(FILE_URI_LOCAL.Length);
}
// If it is a UNC file URI, convert it to its filename, without invoking Uri class.
// example: "file://server/share/csc.exe"
else if (StringUtil.StartsWithIgnoreCase(applicationUri, FILE_URI_UNC)) {
isFile = true;
applicationUri = applicationUri.Substring(FILE_URI.Length);
}
if (isFile) {
applicationUri = applicationUri.Replace('/', '\\');
applicationFilename = applicationUri;
}
else {
applicationUri = exeAssembly.EscapedCodeBase;
}
}
else {
StringBuilder sb = new StringBuilder(MAX_PATH);
int noOfTimes = 1;
int length = 0;
// Iterating by allocating chunk of memory each time we find the length is not sufficient.
// Performance should not be an issue for current MAX_PATH length due to this change.
while (((length = UnsafeNativeMethods.GetModuleFileName(new HandleRef(null, IntPtr.Zero), sb, sb.Capacity)) == sb.Capacity)
&& Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER
&& sb.Capacity < MAX_UNICODESTRING_LEN) {
noOfTimes += 2; // increasing buffer size by 520 in each iteration - perf.
int capacity = noOfTimes * MAX_PATH < MAX_UNICODESTRING_LEN ? noOfTimes * MAX_PATH : MAX_UNICODESTRING_LEN;
sb.EnsureCapacity(capacity);
}
sb.Length = length;
applicationUri = Path.GetFullPath(sb.ToString());
applicationFilename = applicationUri;
}
}
else {
applicationUri = Path.GetFullPath(exePath);
if (!FileUtil.FileExists(applicationUri, false))
throw ExceptionUtil.ParameterInvalid("exePath");
applicationFilename = applicationUri;
}
// Fallback if we haven't set the app config file path yet.
if (_applicationConfigUri == null) {
_applicationConfigUri = applicationUri + ConfigExtension;
}
// Set application path
_applicationUri = applicationUri;
// In the case when exePath was explicitly supplied, we will not be able to
// construct user.config paths, so quit here.
if (exePath != null) {
return;
}
// Skip expensive initialization of user config file information if requested.
if (!_includesUserConfig) {
return;
}
bool isHttp = StringUtil.StartsWithIgnoreCase(_applicationConfigUri, HTTP_URI);
SetNamesAndVersion(applicationFilename, exeAssembly, isHttp);
// Check if this is a clickonce deployed application. If so, point the user config
// files to the clickonce data directory.
if (this.IsClickOnceDeployed(AppDomain.CurrentDomain)) {
string dataPath = AppDomain.CurrentDomain.GetData(ClickOnceDataDirectory) as string;
string versionSuffix = Validate(_productVersion, false);
// NOTE: No roaming config for clickonce - not supported.
if (Path.IsPathRooted(dataPath)) {
_localConfigDirectory = CombineIfValid(dataPath, versionSuffix);
_localConfigFilename = CombineIfValid(_localConfigDirectory, UserConfigFilename);
}
}
else if (!isHttp) {
// If we get the config from http, we do not have a roaming or local config directory,
// as it cannot be edited by the app in those cases because it does not have Full Trust.
// suffix for user config paths
string part1 = Validate(_companyName, true);
string validAppDomainName = Validate(AppDomain.CurrentDomain.FriendlyName, true);
string applicationUriLower = !String.IsNullOrEmpty(_applicationUri) ? _applicationUri.ToLower(CultureInfo.InvariantCulture) : null;
string namePrefix = !String.IsNullOrEmpty(validAppDomainName) ? validAppDomainName : Validate(_productName, true);
string hashSuffix = GetTypeAndHashSuffix(AppDomain.CurrentDomain, applicationUriLower);
string part2 = (!String.IsNullOrEmpty(namePrefix) && !String.IsNullOrEmpty(hashSuffix)) ? namePrefix + hashSuffix : null;
string part3 = Validate(_productVersion, false);
string dirSuffix = CombineIfValid(CombineIfValid(part1, part2), part3);
string roamingFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (Path.IsPathRooted(roamingFolderPath)) {
_roamingConfigDirectory = CombineIfValid(roamingFolderPath, dirSuffix);
_roamingConfigFilename = CombineIfValid(_roamingConfigDirectory, UserConfigFilename);
}
string localFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (Path.IsPathRooted(localFolderPath)) {
_localConfigDirectory = CombineIfValid(localFolderPath, dirSuffix);
_localConfigFilename = CombineIfValid(_localConfigDirectory, UserConfigFilename);
}
}
}
internal static ClientConfigPaths GetPaths(string exePath, bool includeUserConfig) {
ClientConfigPaths result = null;
if (exePath == null) {
if (s_current == null || (includeUserConfig && !s_currentIncludesUserConfig)) {
s_current = new ClientConfigPaths(null, includeUserConfig);
s_currentIncludesUserConfig = includeUserConfig;
}
result = s_current;
}
else {
result = new ClientConfigPaths(exePath, includeUserConfig);
}
return result;
}
internal static void RefreshCurrent() {
s_currentIncludesUserConfig = false;
s_current = null;
}
internal static ClientConfigPaths Current {
get {
return GetPaths(null, true);
}
}
internal bool HasEntryAssembly {
get {
return _hasEntryAssembly;
}
}
internal string ApplicationUri {
get {
return _applicationUri;
}
}
internal string ApplicationConfigUri {
get {
return _applicationConfigUri;
}
}
internal string RoamingConfigFilename {
get {
return _roamingConfigFilename;
}
}
internal string RoamingConfigDirectory {
get {
return _roamingConfigDirectory;
}
}
internal bool HasRoamingConfig {
get {
// Assume we have roaming config if we haven't loaded user config file information.
return RoamingConfigFilename != null || !_includesUserConfig;
}
}
internal string LocalConfigFilename {
get {
return _localConfigFilename;
}
}
internal string LocalConfigDirectory {
get {
return _localConfigDirectory;
}
}
internal bool HasLocalConfig {
get {
// Assume we have roaming config if we haven't loaded user config file information.
return LocalConfigFilename != null || !_includesUserConfig;
}
}
internal string ProductName {
get {
return _productName;
}
}
internal string ProductVersion {
get {
return _productVersion;
}
}
private static SecurityPermission ControlEvidencePermission {
get {
if (s_controlEvidencePerm == null) {
s_controlEvidencePerm = new SecurityPermission(SecurityPermissionFlag.ControlEvidence);
}
return s_controlEvidencePerm;
}
}
private static SecurityPermission SerializationFormatterPermission {
get {
if (s_serializationPerm == null) {
s_serializationPerm = new SecurityPermission(SecurityPermissionFlag.SerializationFormatter);
}
return s_serializationPerm;
}
}
// Combines path2 with path1 if possible, else returns null.
private string CombineIfValid(string path1, string path2) {
string returnPath = null;
if (path1 != null && path2 != null) {
try {
string combinedPath = Path.Combine(path1, path2);
if (combinedPath.Length < MAX_PATH) {
returnPath = combinedPath;
}
}
catch {
}
}
return returnPath;
}
// Returns a type and hash suffix based on app domain evidence. The evidence we use, in
// priority order, is Strong Name, Url and Exe Path. If one of these is found, we compute a
// SHA1 hash of it and return a suffix based on that. If none is found, we return null.
private string GetTypeAndHashSuffix(AppDomain appDomain, string exePath) {
string suffix = null;
string typeName = null;
object evidenceObj = null;
evidenceObj = GetEvidenceInfo(appDomain, exePath, out typeName);
if (evidenceObj != null && !String.IsNullOrEmpty(typeName)) {
MemoryStream ms = new MemoryStream();
BinaryFormatter bSer = new BinaryFormatter();
SerializationFormatterPermission.Assert();
bSer.Serialize(ms, evidenceObj);
ms.Position = 0;
string evidenceHash = GetHash(ms);
if (!String.IsNullOrEmpty(evidenceHash)) {
suffix = "_" + typeName + "_" + evidenceHash;
}
}
return suffix;
}
// Mostly borrowed from IsolatedStorage, with some modifications
private static object GetEvidenceInfo(AppDomain appDomain, string exePath, out string typeName) {
ControlEvidencePermission.Assert();
Evidence evidence = appDomain.Evidence;
StrongName sn = null;
Url url = null;
if (evidence != null) {
IEnumerator e = evidence.GetHostEnumerator();
object temp = null;
while (e.MoveNext()) {
temp = e.Current;
if (temp is StrongName) {
sn = (StrongName) temp;
break;
}
else if (temp is Url) {
url = (Url) temp;
}
}
}
object o = null;
// The order of preference is StrongName, Url, ExePath.
if (sn != null) {
o = MakeVersionIndependent(sn);
typeName = StrongNameDesc;
}
else if (url != null) {
// Extract the url string and normalize it to use as evidence
o = url.Value.ToUpperInvariant();
typeName = UrlDesc;
}
else if (exePath != null) {
o = exePath;
typeName = PathDesc;
}
else {
typeName = null;
}
return o;
}
[SuppressMessage("Microsoft.Security.Cryptography", "CA5354:SHA1CannotBeUsed",
Justification = "Input for the SHA1 hash computation is safe, it is a Strong Name, a Url or an Exe Path, and comes from Assembly.GetEntryAssembly(); or from AppDomain.CurrentDomain.Evidence. This hash is used to persist application settings between application runs, if we change algorithm, applications that rely on this feature will loose user settings.")]
private static String GetHash(Stream s) {
byte[] hash;
using (SHA1 sha1 = new SHA1CryptoServiceProvider()) {
hash = sha1.ComputeHash(s);
}
return ToBase32StringSuitableForDirName(hash);
}
private bool IsClickOnceDeployed(AppDomain appDomain) {
// NOTE: For perf & servicing reasons, we don't want to introduce a dependency on
// System.Deployment.dll here. The following code is an alternative to calling
// ApplicationDeployment.IsNetworkDeployed.
ActivationContext actCtx = appDomain.ActivationContext;
// Ensures the app is running with a context from the store.
if (actCtx != null && actCtx.Form == ActivationContext.ContextForm.StoreBounded) {
string fullAppId = actCtx.Identity.FullName;
if (!String.IsNullOrEmpty(fullAppId)) {
return true;
}
}
return false;
}
private static StrongName MakeVersionIndependent(StrongName sn) {
return new StrongName(sn.PublicKey, sn.Name, new Version(0,0,0,0));
}
private void SetNamesAndVersion(string applicationFilename, Assembly exeAssembly, bool isHttp) {
Type mainType = null;
//
// Get CompanyName, ProductName, and ProductVersion
// First try custom attributes on the assembly.
//
if (exeAssembly != null) {
object[] attrs = exeAssembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
if (attrs != null && attrs.Length > 0) {
_companyName = ((AssemblyCompanyAttribute)attrs[0]).Company;
if (_companyName != null) {
_companyName = _companyName.Trim();
}
}
attrs = exeAssembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false);
if (attrs != null && attrs.Length > 0) {
_productName = ((AssemblyProductAttribute)attrs[0]).Product;
if (_productName != null) {
_productName = _productName.Trim();
}
}
_productVersion = exeAssembly.GetName().Version.ToString();
if (_productVersion != null) {
_productVersion = _productVersion.Trim();
}
}
//
// If we couldn't get custom attributes, try the Win32 file version
//
if (!isHttp && (String.IsNullOrEmpty(_companyName) || String.IsNullOrEmpty(_productName) || String.IsNullOrEmpty(_productVersion))) {
string versionInfoFileName = null;
if (exeAssembly != null) {
MethodInfo entryPoint = exeAssembly.EntryPoint;
if (entryPoint != null) {
mainType = entryPoint.ReflectedType;
if (mainType != null) {
versionInfoFileName = mainType.Module.FullyQualifiedName;
}
}
}
if (versionInfoFileName == null) {
versionInfoFileName = applicationFilename;
}
if (versionInfoFileName != null) {
System.Diagnostics.FileVersionInfo version = System.Diagnostics.FileVersionInfo.GetVersionInfo(versionInfoFileName);
if (version != null) {
if (String.IsNullOrEmpty(_companyName)) {
_companyName = version.CompanyName;
if (_companyName != null) {
_companyName = _companyName.Trim();
}
}
if (String.IsNullOrEmpty(_productName)) {
_productName = version.ProductName;
if (_productName != null) {
_productName = _productName.Trim();
}
}
if (String.IsNullOrEmpty(_productVersion)) {
_productVersion = version.ProductVersion;
if (_productVersion != null) {
_productVersion = _productVersion.Trim();
}
}
}
}
}
if (String.IsNullOrEmpty(_companyName) || String.IsNullOrEmpty(_productName)) {
string ns = null;
if (mainType != null) {
ns = mainType.Namespace;
}
// Desperate measures for product name
if (String.IsNullOrEmpty(_productName)) {
// Try the remainder of the namespace
if (ns != null) {
int lastDot = ns.LastIndexOf(".", StringComparison.Ordinal);
if (lastDot != -1 && lastDot < ns.Length - 1) {
_productName = ns.Substring(lastDot+1);
}
else {
_productName = ns;
}
_productName = _productName.Trim();
}
// Try the type of the entry assembly
if (String.IsNullOrEmpty(_productName) && mainType != null) {
_productName = mainType.Name.Trim();
}
// give up, return empty string
if (_productName == null) {
_productName = string.Empty;
}
}
// Desperate measures for company name
if (String.IsNullOrEmpty(_companyName)) {
// Try the first part of the namespace
if (ns != null) {
int firstDot = ns.IndexOf(".", StringComparison.Ordinal);
if (firstDot != -1) {
_companyName = ns.Substring(0, firstDot);
}
else {
_companyName = ns;
}
_companyName = _companyName.Trim();
}
// If that doesn't work, use the product name
if (String.IsNullOrEmpty(_companyName)) {
_companyName = _productName;
}
}
}
// Desperate measures for product version - assume 1.0
if (String.IsNullOrEmpty(_productVersion)) {
_productVersion = "1.0.0.0";
}
}
// Borrowed from IsolatedStorage
private static string ToBase32StringSuitableForDirName(byte[] buff) {
StringBuilder sb = new StringBuilder();
byte b0, b1, b2, b3, b4;
int l, i;
l = buff.Length;
i = 0;
// Create l chars using the last 5 bits of each byte.
// Consume 3 MSB bits 5 bytes at a time.
do
{
b0 = (i < l) ? buff[i++] : (byte)0;
b1 = (i < l) ? buff[i++] : (byte)0;
b2 = (i < l) ? buff[i++] : (byte)0;
b3 = (i < l) ? buff[i++] : (byte)0;
b4 = (i < l) ? buff[i++] : (byte)0;
// Consume the 5 Least significant bits of each byte
sb.Append(s_Base32Char[b0 & 0x1F]);
sb.Append(s_Base32Char[b1 & 0x1F]);
sb.Append(s_Base32Char[b2 & 0x1F]);
sb.Append(s_Base32Char[b3 & 0x1F]);
sb.Append(s_Base32Char[b4 & 0x1F]);
// Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
sb.Append(s_Base32Char[(
((b0 & 0xE0) >> 5) |
((b3 & 0x60) >> 2))]);
sb.Append(s_Base32Char[(
((b1 & 0xE0) >> 5) |
((b4 & 0x60) >> 2))]);
// Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
b2 >>= 5;
if ((b3 & 0x80) != 0)
b2 |= 0x08;
if ((b4 & 0x80) != 0)
b2 |= 0x10;
sb.Append(s_Base32Char[b2]);
} while (i < l);
return sb.ToString();
}
// Makes the passed in string suitable to use as a path name by replacing illegal characters
// with underscores. Additionally, we do two things - replace spaces too with underscores and
// limit the resultant string's length to MAX_LENGTH_TO_USE if limitSize is true.
private string Validate(string str, bool limitSize) {
string validated = str;
if (!String.IsNullOrEmpty(validated)) {
// First replace all illegal characters with underscores
foreach (char c in Path.GetInvalidFileNameChars()) {
validated = validated.Replace(c, '_');
}
// Replace all spaces with underscores
validated = validated.Replace(' ', '_');
if (limitSize) {
validated = (validated.Length > MAX_LENGTH_TO_USE) ? validated.Substring(0, MAX_LENGTH_TO_USE) : validated;
}
}
return validated;
}
}
}
|