/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using dnlib.DotNet;
using dnSpy.Decompiler.Properties;
namespace dnSpy.Decompiler.MSBuild {
sealed class SettingsProjectFile : ProjectFile {
public override string Description => string.Format(dnSpy_Decompiler_Resources.MSBuild_CreateSettingsFile, Path.GetFileName(filename));
public override BuildAction BuildAction => BuildAction.None;
public override string Filename => filename;
readonly string filename;
readonly TypeDef type;
public SettingsProjectFile(TypeDef type, string filename) {
this.filename = filename;
this.type = type;
}
sealed class Setting {
public string? Name { get; set; }
public string? Description { get; set; }
public string? Provider { get; set; }
public bool Roaming { get; set; }
public bool GenerateDefaultValueInCode { get; set; }
public string? Type { get; set; }
public string? Scope { get; set; }
public Value? Value { get; set; }
public Value? DesignTimeValue { get; set; }
public Setting() => GenerateDefaultValueInCode = true;
}
sealed class Value {
public string? Profile { get; set; }
public string? Text { get; set; }
}
const string DEFAULT_PROFILE = "(Default)";
public override void Create(DecompileContext ctx) {
var settings = new XmlWriterSettings {
Encoding = Encoding.UTF8,
Indent = true,
};
using (var writer = XmlWriter.Create(filename, settings)) {
writer.WriteProcessingInstruction("xml", "version='1.0' encoding='utf-8'");
writer.WriteStartElement("SettingsFile", "http://schemas.microsoft.com/VisualStudio/2004/01/settings");
writer.WriteAttributeString("CurrentProfile", DEFAULT_PROFILE);
writer.WriteAttributeString("GeneratedClassNamespace", type.ReflectionNamespace);
writer.WriteAttributeString("GeneratedClassName", type.ReflectionName);
writer.WriteStartElement("Profiles");
writer.WriteEndElement();
writer.WriteStartElement("Settings");
foreach (var setting in FindSettings()) {
writer.WriteStartElement("Setting");
writer.WriteAttributeString("Name", setting.Name);
if (!string.IsNullOrEmpty(setting.Description))
writer.WriteAttributeString("Description", setting.Description);
if (!string.IsNullOrEmpty(setting.Provider))
writer.WriteAttributeString("Provider", setting.Provider);
if (setting.Roaming)
writer.WriteAttributeString("Roaming", "true");
if (!setting.GenerateDefaultValueInCode)
writer.WriteAttributeString("GenerateDefaultValueInCode", "false");
writer.WriteAttributeString("Type", setting.Type);
writer.WriteAttributeString("Scope", setting.Scope);
if (setting.DesignTimeValue is not null) {
writer.WriteStartElement("DesignTimeValue");
writer.WriteAttributeString("Profile", setting.DesignTimeValue.Profile);
writer.WriteString(setting.DesignTimeValue.Text);
writer.WriteEndElement();
}
writer.WriteStartElement("Value");
writer.WriteAttributeString("Profile", setting.Value?.Profile ?? "???");
writer.WriteString(setting.Value?.Text ?? "???");
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
IEnumerable FindSettings() {
foreach (var prop in type.Properties) {
var propType = prop.PropertySig.GetRetType().RemovePinnedAndModifiers();
if (propType is null)
continue;
string settingsType = propType.ReflectionFullName;
var ca = prop.CustomAttributes.Find("System.Configuration.DefaultSettingValueAttribute");
if (ca is null || ca.ConstructorArguments.Count != 1)
continue;
var arg = ca.ConstructorArguments[0];
if (arg.Type.RemovePinnedAndModifiers().GetElementType() != ElementType.String)
continue;
string defaultValue = arg.Value as UTF8String;
if (defaultValue is null)
continue;
bool generateDefaultValueInCode = true;
bool hasUserScopedAttr = prop.CustomAttributes.IsDefined("System.Configuration.UserScopedSettingAttribute");
bool hasAppScopedAttr = prop.CustomAttributes.IsDefined("System.Configuration.ApplicationScopedSettingAttribute");
if (!hasUserScopedAttr && !hasAppScopedAttr)
continue;
bool roaming = false;
ca = prop.CustomAttributes.Find("System.Configuration.SettingsManageabilityAttribute");
if (ca is not null && ca.ConstructorArguments.Count == 1) {
arg = ca.ConstructorArguments[0];
var argType = arg.Type.RemovePinnedAndModifiers();
if (argType is not null && argType.ReflectionFullName == "System.Configuration.SettingsManageability") {
var v = arg.Value as int?;
if (v is not null) {
switch ((SettingsManageability)v.Value) {
case SettingsManageability.Roaming:
roaming = true;
break;
}
}
}
}
var setting = new Setting();
ca = prop.CustomAttributes.Find("System.Configuration.SpecialSettingAttribute");
if (ca is not null && ca.ConstructorArguments.Count == 1) {
arg = ca.ConstructorArguments[0];
var argType = arg.Type.RemovePinnedAndModifiers();
if (argType is not null && argType.ReflectionFullName == "System.Configuration.SpecialSetting") {
var v = arg.Value as int?;
if (v is not null) {
switch ((SpecialSetting)v.Value) {
case SpecialSetting.ConnectionString:
settingsType = "(Connection string)";
var designTimeValue = GetConnectionStringDesignTimeValue(prop);
if (designTimeValue is not null) {
setting.DesignTimeValue = new Value {
Profile = DEFAULT_PROFILE,
Text = designTimeValue,
};
}
break;
case SpecialSetting.WebServiceUrl:
settingsType = "(Web Service URL)";
break;
}
}
}
}
string? provider = null;
ca = prop.CustomAttributes.Find("System.Configuration.SettingsProviderAttribute");
if (ca is not null && ca.ConstructorArguments.Count == 1) {
arg = ca.ConstructorArguments[0];
var argType = arg.Type.RemovePinnedAndModifiers();
if (argType.GetElementType() == ElementType.String)
provider = arg.Value as UTF8String;
else if (argType is not null && argType.FullName == "System.Type") {
if (arg.Value is TypeDefOrRefSig t && t.TypeDefOrRef is not null)
provider = t.TypeDefOrRef.ReflectionFullName;
}
}
string? description = null;
ca = prop.CustomAttributes.Find("System.Configuration.SettingsDescriptionAttribute");
if (ca is not null && ca.ConstructorArguments.Count == 1) {
arg = ca.ConstructorArguments[0];
var argType = arg.Type.RemovePinnedAndModifiers();
if (argType.GetElementType() == ElementType.String)
description = arg.Value as UTF8String;
}
setting.Name = prop.Name;
setting.Description = description;
setting.Provider = provider;
setting.Roaming = roaming;
setting.GenerateDefaultValueInCode = generateDefaultValueInCode;
setting.Type = settingsType;
setting.Scope = hasUserScopedAttr ? "User" : "Application";
setting.Value = new Value {
Profile = DEFAULT_PROFILE,
Text = defaultValue,
};
yield return setting;
}
}
string? GetConnectionStringDesignTimeValue(PropertyDef prop) {
if (toConnectionStringInfo is null)
InitializeConnectionStringDesignTimeValues();
Debug2.Assert(toConnectionStringInfo is not null);
if (!toConnectionStringInfo.TryGetValue(prop.Name, out var info))
return null;
return string.Format(connectionIdStringFormat, EscapeXmlString(info.String), EscapeXmlString(info.ProviderName));
}
Dictionary? toConnectionStringInfo;
static readonly string connectionIdStringFormat = "\r\n\r\n {0}\r\n {1}\r\n";
static string EscapeXmlString(string? s) {
var el = new XmlDocument().CreateElement("a");
el.InnerText = s ?? string.Empty;
return el.InnerXml;
}
sealed class ConnectionStringInfo {
public string? String { get; set; }
public string? ProviderName { get; set; }
}
void InitializeConnectionStringDesignTimeValues() {
Debug2.Assert(toConnectionStringInfo is null);
if (toConnectionStringInfo is not null)
return;
toConnectionStringInfo = new Dictionary(StringComparer.Ordinal);
var configFile = type.Module.Location + ".config";
if (!File.Exists(configFile))
return;
try {
var doc = XDocument.Load(configFile, LoadOptions.None);
var prefix = type.ReflectionFullName + ".";
foreach (var e in doc.XPathSelectElements("/configuration/connectionStrings/add")) {
var name = (string?)e.Attribute("name");
if (name is null || !name.StartsWith(prefix, StringComparison.Ordinal))
continue;
var connectionString = (string?)e.Attribute("connectionString");
var providerName = (string?)e.Attribute("providerName");
if (connectionString is null || providerName is null)
continue;
var info = new ConnectionStringInfo {
String = connectionString,
ProviderName = providerName,
};
var propName = name.Substring(prefix.Length);
if (!toConnectionStringInfo.ContainsKey(propName))
toConnectionStringInfo[propName] = info;
}
}
catch {
}
}
}
}