|
//------------------------------------------------------------------------------
// <copyright file="BaseConfigurationRecord.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Configuration {
using System.Configuration.Internal;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security;
using System.Text;
using System.Threading;
using System.Xml;
using System.Runtime.Versioning;
//
// This object represents the configuration for a request path, and is cached per-path.
//
[System.Diagnostics.DebuggerDisplay("ConfigPath = {ConfigPath}")]
abstract internal class BaseConfigurationRecord : IInternalConfigRecord {
protected const string NL = "\r\n";
internal const string KEYWORD_TRUE = "true";
internal const string KEYWORD_FALSE = "false";
protected const string KEYWORD_CONFIGURATION = "configuration";
protected const string KEYWORD_CONFIGURATION_NAMESPACE = "http://schemas.microsoft.com/.NetConfiguration/v2.0";
protected const string KEYWORD_CONFIGSECTIONS = "configSections";
protected const string KEYWORD_SECTION = "section";
protected const string KEYWORD_SECTION_NAME = "name";
protected const string KEYWORD_SECTION_TYPE = "type";
protected const string KEYWORD_SECTION_ALLOWLOCATION = "allowLocation";
protected const string KEYWORD_SECTION_ALLOWDEFINITION = "allowDefinition";
protected const string KEYWORD_SECTION_ALLOWDEFINITION_EVERYWHERE = "Everywhere";
protected const string KEYWORD_SECTION_ALLOWDEFINITION_MACHINEONLY = "MachineOnly";
protected const string KEYWORD_SECTION_ALLOWDEFINITION_MACHINETOAPPLICATION = "MachineToApplication";
protected const string KEYWORD_SECTION_ALLOWDEFINITION_MACHINETOWEBROOT = "MachineToWebRoot";
protected const string KEYWORD_SECTION_ALLOWEXEDEFINITION = "allowExeDefinition";
protected const string KEYWORD_SECTION_ALLOWEXEDEFINITION_MACHTOROAMING = "MachineToRoamingUser";
protected const string KEYWORD_SECTION_ALLOWEXEDEFINITION_MACHTOLOCAL = "MachineToLocalUser";
protected const string KEYWORD_SECTION_RESTARTONEXTERNALCHANGES = "restartOnExternalChanges";
protected const string KEYWORD_SECTION_REQUIREPERMISSION = "requirePermission";
protected const string KEYWORD_SECTIONGROUP = "sectionGroup";
protected const string KEYWORD_SECTIONGROUP_NAME = "name";
protected const string KEYWORD_SECTIONGROUP_TYPE = "type";
protected const string KEYWORD_REMOVE = "remove";
protected const string KEYWORD_CLEAR = "clear";
protected const string KEYWORD_LOCATION = "location";
protected const string KEYWORD_LOCATION_PATH = "path";
internal const string KEYWORD_LOCATION_ALLOWOVERRIDE = "allowOverride";
protected const string KEYWORD_LOCATION_INHERITINCHILDAPPLICATIONS = "inheritInChildApplications";
protected const string KEYWORD_CONFIGSOURCE = "configSource";
protected const string KEYWORD_XMLNS = "xmlns";
protected const string KEYWORD_CONFIG_BUILDER = "configBuilders";
internal const string KEYWORD_PROTECTION_PROVIDER = "configProtectionProvider";
protected const string FORMAT_NEWCONFIGFILE = "<?xml version=\"1.0\" encoding=\"{0}\"?>\r\n";
protected const string FORMAT_CONFIGURATION = "<configuration>\r\n";
protected const string FORMAT_CONFIGURATION_NAMESPACE = "<configuration xmlns=\"{0}\">\r\n";
protected const string FORMAT_CONFIGURATION_ENDELEMENT = "</configuration>";
internal const string KEYWORD_SECTION_OVERRIDEMODEDEFAULT = "overrideModeDefault";
internal const string KEYWORD_LOCATION_OVERRIDEMODE = "overrideMode";
internal const string KEYWORD_OVERRIDEMODE_INHERIT = "Inherit";
internal const string KEYWORD_OVERRIDEMODE_ALLOW = "Allow";
internal const string KEYWORD_OVERRIDEMODE_DENY = "Deny";
protected const string FORMAT_LOCATION_NOPATH = "<location {0} inheritInChildApplications=\"{1}\">\r\n";
protected const string FORMAT_LOCATION_PATH = "<location path=\"{2}\" {0} inheritInChildApplications=\"{1}\">\r\n";
protected const string FORMAT_LOCATION_ENDELEMENT = "</location>";
internal const string KEYWORD_LOCATION_OVERRIDEMODE_STRING = "{0}=\"{1}\"";
protected const string FORMAT_SECTION_CONFIGSOURCE = "<{0} configSource=\"{1}\" />";
protected const string FORMAT_CONFIGSOURCE_FILE = "<?xml version=\"1.0\" encoding=\"{0}\"?>\r\n";
protected const string FORMAT_SECTIONGROUP_ENDELEMENT = "</sectionGroup>";
// Class flags should only be used with the ClassFlags property.
protected const int ClassSupportsChangeNotifications = 0x00000001;
protected const int ClassSupportsRefresh = 0x00000002;
protected const int ClassSupportsImpersonation = 0x00000004;
protected const int ClassSupportsRestrictedPermissions = 0x00000008;
protected const int ClassSupportsKeepInputs = 0x00000010;
protected const int ClassSupportsDelayedInit = 0x00000020;
protected const int ClassIgnoreLocalErrors = 0x00000040;
// Flags to use with the _flags field.
protected const int ProtectedDataInitialized = 0x00000001;
protected const int Closed = 0x00000002;
protected const int PrefetchAll = 0x00000008;
protected const int IsAboveApplication = 0x00000020;
private const int ContextEvaluated = 0x00000080;
private const int IsLocationListResolved = 0x00000100;
protected const int NamespacePresentInFile = 0x00000200;
private const int RestrictedPermissionsResolved = 0x00000800;
protected const int IsTrusted = 0x00002000;
protected const int SupportsChangeNotifications = 0x00010000;
protected const int SupportsRefresh = 0x00020000;
protected const int SupportsPath = 0x00040000;
protected const int SupportsKeepInputs = 0x00080000;
protected const int SupportsLocation = 0x00100000;
// Flags for Mgmt Configuration Record
protected const int ForceLocationWritten = 0x01000000;
protected const int SuggestLocationRemoval = 0x02000000;
protected const int NamespacePresentCurrent = 0x04000000;
protected const int ConfigBuildersInitialized = 0x08000000;
internal const char ConfigPathSeparatorChar = '/';
internal const string ConfigPathSeparatorString = "/";
static internal readonly char[] ConfigPathSeparatorParams = new char[] {ConfigPathSeparatorChar};
private static volatile ConfigurationPermission s_unrestrictedConfigPermission; // cached ConfigurationPermission
protected SafeBitVector32 _flags; // state
protected BaseConfigurationRecord _parent; // parent record
protected Hashtable _children; // configName -> record
protected InternalConfigRoot _configRoot; // root of configuration
protected string _configName; // the last part of the config path
protected string _configPath; // the full config path
protected string _locationSubPath; // subPath for the config record when editing a location configuration
private ConfigRecordStreamInfo _configStreamInfo; // stream info for the config record
private object _configContext; // Context for config level
private ConfigurationBuildersSection _configBuilders; // section containing the general config builders
private ProtectedConfigurationSection _protectedConfig; // section containing the encryption providers
private PermissionSet _restrictedPermissions; // cached restricted permission set
private ConfigurationSchemaErrors _initErrors; // errors encountered during the parse of the configuration file
private BaseConfigurationRecord _initDelayedRoot; // root of delayed initialization
// Records information about <configSections> present in this web.config file.
// config key -> FactoryRecord
protected Hashtable _factoryRecords;
// Records information about sections that apply to this path,
// which may be found in this web.config file, in a parent through
// inheritance, or in a parent through <location>
// config key -> SectionRecord
protected Hashtable _sectionRecords;
// Records information about sections in a <location> directive
// that do not apply to this configPath (sections where path != ".")
protected ArrayList _locationSections;
// WOS 1955773: (Perf) 4,000 location sections in web.config file degrades working set
private static string s_appConfigPath;
// Comparer used in sorting IndirectInputs.
private static IComparer<SectionInput> s_indirectInputsComparer = new IndirectLocationInputComparer();
internal BaseConfigurationRecord() {
// not strictly necessary, but compiler spits out a warning without this initiailization
_flags = new SafeBitVector32();
}
// Class flags
protected abstract SimpleBitVector32 ClassFlags {get;}
// Create the factory that will evaluate configuration
protected abstract object CreateSectionFactory(FactoryRecord factoryRecord);
// Create the configuration object
protected abstract object CreateSection(bool inputIsTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, SectionInput sectionInput, object parentConfig, ConfigXmlReader reader);
// Use the parent result in creating the child
protected abstract object UseParentResult(string configKey, object parentResult, SectionRecord sectionRecord);
// Return the runtime object from GetSection
protected abstract object GetRuntimeObject(object result);
//
// IInternalConfigRecord methods
//
public string ConfigPath {
get {return _configPath;}
}
public string StreamName {
get {return ConfigStreamInfo.StreamName;}
}
public bool HasInitErrors {
get {
return _initErrors.HasErrors(ClassFlags[ClassIgnoreLocalErrors]);
}
}
public void ThrowIfInitErrors() {
ThrowIfParseErrors(_initErrors);
}
public object GetSection(string configKey) {
#if DBG
// On debug builds, the config system depends on system.diagnostics,
// so we must always return a valid result and never throw.
if (configKey == "system.diagnostics" && !ClassFlags[ClassIgnoreLocalErrors]) {
return GetSection(configKey, true, true);
}
else {
return GetSection(configKey, false, true);
}
#else
return GetSection(configKey, false, true);
#endif
}
public object GetLkgSection(string configKey) {
return GetSection(configKey, true, true);
}
public void RefreshSection(string configKey) {
_configRoot.ClearResult(this, configKey, true);
}
public void Remove() {
_configRoot.RemoveConfigRecord(this);
}
//
// end of IInternalConfigRecord methods
//
internal bool HasStream {
get { return ConfigStreamInfo.HasStream; }
}
// Determine which sections should be prefetched during the first scan.
private bool ShouldPrefetchRawXml(FactoryRecord factoryRecord) {
if (_flags[PrefetchAll])
return true;
switch (factoryRecord.ConfigKey) {
case BaseConfigurationRecord.RESERVED_SECTION_PROTECTED_CONFIGURATION:
case "system.diagnostics":
case "appSettings":
case "connectionStrings":
return true;
}
return Host.PrefetchSection(factoryRecord.Group, factoryRecord.Name);
}
protected IDisposable Impersonate() {
IDisposable context = null;
if (ClassFlags[ClassSupportsImpersonation]) {
context = Host.Impersonate();
}
if (context == null) {
context = EmptyImpersonationContext.GetStaticInstance();
}
return context;
}
internal PermissionSet GetRestrictedPermissions()
{
if (!_flags[RestrictedPermissionsResolved])
{
lock (this)
{
if (!_flags[RestrictedPermissionsResolved])
{
if (AppDomain.CurrentDomain.IsHomogenous)
{
// in a homogenous domain, just call PermitOnly() on the AppDomain's existing permission set
_restrictedPermissions = AppDomain.CurrentDomain.PermissionSet;
_flags[RestrictedPermissionsResolved] = true;
}
else
{
// in a non-homogenous domain, use Evidence to calculate the current security policy
PermissionSet restrictedPermissions;
bool isHostReady;
GetRestrictedPermissionsWithAssert(out restrictedPermissions, out isHostReady);
if (isHostReady)
{
_restrictedPermissions = restrictedPermissions;
_flags[RestrictedPermissionsResolved] = true;
}
}
// calling PermitOnly() on an unrestricted PermissionSet is a no-op
if (_restrictedPermissions != null && _restrictedPermissions.IsUnrestricted())
{
_restrictedPermissions = null;
}
}
}
}
return _restrictedPermissions;
}
// Getting the PermissionSet object is a FullTrust operation; we can Assert since our callers are careful not to leak it
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
private void GetRestrictedPermissionsWithAssert(out PermissionSet permissionSet, out bool isHostReady) {
Host.GetRestrictedPermissions(this, out permissionSet, out isHostReady);
}
internal void Init(
IInternalConfigRoot configRoot,
BaseConfigurationRecord parent,
string configPath,
string locationSubPath) {
_initErrors = new ConfigurationSchemaErrors();
//
// try/catch here is only for unexpected exceptions due to errors in
// our own code, as we always want the configuration record to be
// usable
//
try {
_configRoot = (InternalConfigRoot) configRoot;
_parent = parent;
_configPath = configPath;
_locationSubPath = locationSubPath;
_configName = ConfigPathUtility.GetName(configPath);
if (IsLocationConfig) {
_configStreamInfo = _parent.ConfigStreamInfo;
}
else {
_configStreamInfo = new ConfigRecordStreamInfo();
}
// no more initialization in case of root config
if (IsRootConfig)
return;
// determine what features we support
_flags[SupportsChangeNotifications] = ClassFlags[ClassSupportsChangeNotifications] && Host.SupportsChangeNotifications;
_flags[SupportsRefresh] = ClassFlags[ClassSupportsRefresh] && Host.SupportsRefresh;
_flags[SupportsKeepInputs] = ClassFlags[ClassSupportsKeepInputs] || _flags[SupportsRefresh];
_flags[SupportsPath] = Host.SupportsPath;
_flags[SupportsLocation] = Host.SupportsLocation;
// get static state based on the configPath
if (_flags[SupportsLocation]) {
_flags[IsAboveApplication] = Host.IsAboveApplication(_configPath);
}
_flags[IsTrusted] = Host.IsTrustedConfigPath(_configPath);
ArrayList locationSubPathInputs = null;
if (_flags[SupportsLocation]) {
//
// Treat location inputs from parent record
// as though they were bonafide sections in this record.
//
if (IsLocationConfig && _parent._locationSections != null) {
// Resolve paths and check for errors in location sections.
_parent.ResolveLocationSections();
int i = 0;
while (i < _parent._locationSections.Count) {
LocationSectionRecord locationSectionRecord = (LocationSectionRecord) _parent._locationSections[i];
if (!StringUtil.EqualsIgnoreCase(locationSectionRecord.SectionXmlInfo.TargetConfigPath, this.ConfigPath)) {
i++;
}
else {
// remove the locationSectionRecord from the list
_parent._locationSections.RemoveAt(i);
if (locationSubPathInputs == null) {
locationSubPathInputs = new ArrayList();
}
locationSubPathInputs.Add(locationSectionRecord);
}
}
}
// Handle indirect inputs for location config record.
if (IsLocationConfig && Host.IsLocationApplicable(_configPath)) {
Dictionary<string, List<SectionInput>> indirectLocationInputs = null;
BaseConfigurationRecord current = _parent;
// Note that the code will also go thru all parents, just like what we did
// with the location inputs later. But perf-wise it's okay not to merge the two loops
// because a Configuration object will contain at most one location config record.
while (!current.IsRootConfig) {
if (current._locationSections != null) {
current.ResolveLocationSections();
foreach (LocationSectionRecord locationSectionRecord in current._locationSections) {
//
// VSWhidbey 540184
// If we're initializing a location config record, we will also use those location
// tags that apply to the directories between the parent and the location config
// record's ConfigPath.
//
// This way, the result will contain all the inputs (we've parsed so far) which
// affect the path specified by locationSubPath.
///
// To look for this kind of input, make sure that:
// 1. We are creating a location config
// 2. The location tag's target path lies between parent's config path and ConfigPath
// 2.1: ConfigPath must be a child of the location tag's target path
// 2.2: The target path must be a child of parent's config path
//
// One example of why we need 2.2:
// The root web.config has a location tag with path="1", and we open a configuration
// at "1" with locationSubPath="sub". If we don't have 2.2, then we will incorrectly set
// addInput to true for this location tag. It's incorrect because that location tag
// is already used by configRecord at level "1".
//
// 3. The location tag shouldn't be skipped due to inheritInChildApplications setting
//
if (
// Check #1
IsLocationConfig &&
// Check #2.1
UrlPath.IsSubpath(locationSectionRecord.SectionXmlInfo.TargetConfigPath, ConfigPath) &&
// Check #2.2
UrlPath.IsSubpath(parent.ConfigPath, locationSectionRecord.SectionXmlInfo.TargetConfigPath) &&
// Check #3
!ShouldSkipDueToInheritInChildApplications(locationSectionRecord.SectionXmlInfo.SkipInChildApps, locationSectionRecord.SectionXmlInfo.TargetConfigPath)
) {
//
// In order to separate these kinds of input from "file inputs" and "location inputs"
// we introduce a new kind of input called the "indirect location inputs".
//
// First add all indirect inputs per configKey to a local list.
// We will sort all lists after the while loop.
if (indirectLocationInputs == null) {
indirectLocationInputs = new Dictionary<string, List<SectionInput>>(1);
}
string configKey = locationSectionRecord.SectionXmlInfo.ConfigKey;
if (!((IDictionary)indirectLocationInputs).Contains(configKey)) {
indirectLocationInputs.Add(configKey, new List<SectionInput>(1));
}
indirectLocationInputs[configKey].Add(
new SectionInput(locationSectionRecord.SectionXmlInfo,
locationSectionRecord.ErrorsList));
// copy the initialization errors to the record
if (locationSectionRecord.HasErrors) {
this._initErrors.AddSavedLocalErrors(locationSectionRecord.Errors);
}
}
}
}
current = current._parent;
}
if (indirectLocationInputs != null) {
// Add indirect inputs per configKey
foreach(KeyValuePair<string, List<SectionInput>> keyValuePair in indirectLocationInputs) {
List<SectionInput> inputsPerConfigKey = keyValuePair.Value;
string configKey = keyValuePair.Key;
// We have to sort the indirect inputs
// 1. First by the location tag's target config path, and if they're the same,
// 2. Then by the location tag's definition config path.
inputsPerConfigKey.Sort(s_indirectInputsComparer);
// Add them to the section record.
// In the sorted list, the closest parent is at the beginning of the
// list, which is what we'll add first.
SectionRecord sectionRecord = EnsureSectionRecord(configKey, true);
Debug.Assert(inputsPerConfigKey.Count > 0, "We won't get here unless we have inputs.");
foreach(SectionInput sectionInput in inputsPerConfigKey) {
sectionRecord.AddIndirectLocationInput(sectionInput);
}
DebugValidateIndirectInputs(sectionRecord);
}
}
}
//
// Add location inputs that apply to this path, all the way up the parent hierarchy.
//
if (Host.IsLocationApplicable(_configPath)) {
BaseConfigurationRecord current = _parent;
while (!current.IsRootConfig) {
if (current._locationSections != null) {
current.ResolveLocationSections();
foreach (LocationSectionRecord locationSectionRecord in current._locationSections) {
// We use the location tag input if:
// - The path matches, and
// - It's not skipped due to inheritInChildApplications setting.
if (StringUtil.EqualsIgnoreCase(locationSectionRecord.SectionXmlInfo.TargetConfigPath, this._configPath) &&
!ShouldSkipDueToInheritInChildApplications(locationSectionRecord.SectionXmlInfo.SkipInChildApps)) {
// add the location input for this section
SectionRecord sectionRecord = EnsureSectionRecord(locationSectionRecord.ConfigKey, true);
SectionInput sectionInput = new SectionInput(
locationSectionRecord.SectionXmlInfo, locationSectionRecord.ErrorsList);
sectionRecord.AddLocationInput(sectionInput);
// copy the initialization errors to the record
if (locationSectionRecord.HasErrors) {
this._initErrors.AddSavedLocalErrors(locationSectionRecord.Errors);
}
}
}
}
current = current._parent;
}
}
}
if (!IsLocationConfig) {
//
// If config file exists, open it and parse it once so we can
// find what to evaluate later.
//
InitConfigFromFile();
}
else {
// Add location sections for this record as bonafide sections.
if (locationSubPathInputs != null) {
foreach (LocationSectionRecord locationSectionRecord in locationSubPathInputs) {
// add the file input
SectionRecord sectionRecord = EnsureSectionRecord(locationSectionRecord.ConfigKey, true);
SectionInput sectionInput = new SectionInput(locationSectionRecord.SectionXmlInfo, locationSectionRecord.ErrorsList);
sectionRecord.AddFileInput(sectionInput);
// copy the initialization errors to the record
if (locationSectionRecord.HasErrors) {
this._initErrors.AddSavedLocalErrors(locationSectionRecord.Errors);
}
}
}
}
}
//
// Capture all exceptions and include them in initialization errors.
//
catch (Exception e) {
string streamname = (ConfigStreamInfo != null) ? ConfigStreamInfo.StreamName : null;
_initErrors.AddError(
ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_error_loading_XML_file), e, streamname, 0),
ExceptionAction.Global);
}
}
// InitConfigFromFile
//
// Init the Config From the File.
//
private void InitConfigFromFile() {
bool implicitSectionsAdded = false;
try {
// If initialization should be delayed, do not read the file.
if (ClassFlags[ClassSupportsDelayedInit] && Host.IsInitDelayed(this)) {
// determine the root of delayed initialization
if (_parent._initDelayedRoot == null) {
_initDelayedRoot = this;
}
else {
_initDelayedRoot = _parent._initDelayedRoot;
}
}
else {
//
// This parent of a record that is not initDelayed must also
// not be initDelayed.
//
Debug.Assert(!_parent.IsInitDelayed, "!_parent.IsInitDelayed");
using (Impersonate()) {
//
// Get the stream name. Note that this may be an expensive operation
// for the client configuration host, which is why it comes after the
// check for delayed init.
//
ConfigStreamInfo.StreamName = Host.GetStreamName(_configPath);
if (!String.IsNullOrEmpty(ConfigStreamInfo.StreamName)) {
// Lets listen to the stream
ConfigStreamInfo.StreamVersion = MonitorStream(null, null, ConfigStreamInfo.StreamName);
using (Stream stream = Host.OpenStreamForRead(ConfigStreamInfo.StreamName)) {
if (stream == null) {
// There is no stream to load from
return;
}
ConfigStreamInfo.HasStream = true;
// Determine whether or not to prefetch.
_flags[PrefetchAll] = Host.PrefetchAll(_configPath, ConfigStreamInfo.StreamName);
using (XmlUtil xmlUtil = new XmlUtil(stream, ConfigStreamInfo.StreamName, true, _initErrors)) {
ConfigStreamInfo.StreamEncoding = xmlUtil.Reader.Encoding;
// Read the factories
Hashtable factoryList = ScanFactories(xmlUtil);
_factoryRecords = factoryList;
// Add implicit sections before scanning sections
AddImplicitSections(null);
implicitSectionsAdded = true;
// Read the sections themselves
if (xmlUtil.Reader.Depth == 1) {
ScanSections(xmlUtil);
}
}
}
}
}
}
}
//
// Capture all exceptions and include them in _initErrors so execution in Init can continue.
//
catch (XmlException e) {
//
// Treat invalid XML as unrecoverable by replacing all errors with the XML error.
//
_initErrors.SetSingleGlobalError(
ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_error_loading_XML_file), e, ConfigStreamInfo.StreamName, 0));
}
catch (Exception e) {
_initErrors.AddError(
ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_error_loading_XML_file), e, ConfigStreamInfo.StreamName, 0),
ExceptionAction.Global);
}
//
// If there are global errors that make all input invalid,
// reset our state so that inherited configuration can still be used,
// including location sections that apply to this file.
//
if (_initErrors.HasGlobalErrors) {
//
// Parsing of a section may have been in progress when the exception
// was thrown, so clear any accumulated local errors.
//
_initErrors.ResetLocalErrors();
//
// Stop monitoring any configSource streams, but still continue
// to monitor the main config file if it was successfully monitored.
//
HybridDictionary streamInfos = null;
lock (this) {
if (ConfigStreamInfo.HasStreamInfos) {
streamInfos = ConfigStreamInfo.StreamInfos;
ConfigStreamInfo.ClearStreamInfos();
if (!String.IsNullOrEmpty(ConfigStreamInfo.StreamName)) {
StreamInfo fileStreamInfo = (StreamInfo) streamInfos[ConfigStreamInfo.StreamName];
// add this file's streaminfo to the now empty list
if (fileStreamInfo != null) {
streamInfos.Remove(ConfigStreamInfo.StreamName);
ConfigStreamInfo.StreamInfos.Add(ConfigStreamInfo.StreamName, fileStreamInfo);
}
}
}
}
// Stop monitoring streams outside the lock, to prevent deadlock.
if (streamInfos != null) {
foreach (StreamInfo streamInfo in streamInfos.Values) {
if (streamInfo.IsMonitored) {
Host.StopMonitoringStreamForChanges(streamInfo.StreamName, ConfigStreamInfo.CallbackDelegate);
}
}
}
// Remove file input
if (_sectionRecords != null) {
List<SectionRecord> removes = null;
foreach (SectionRecord sectionRecord in _sectionRecords.Values) {
Debug.Assert(!sectionRecord.HasIndirectLocationInputs,
"Don't expect to have any IndirectLocationInputs because location config record shouldn't call InitConfigFromFile");
if (sectionRecord.HasLocationInputs) {
// Remove any file input
sectionRecord.RemoveFileInput();
}
else {
// Remove the entire section record
if (removes == null) {
removes = new List<SectionRecord>();
}
removes.Add(sectionRecord);
}
}
if (removes != null) {
foreach (SectionRecord sectionRecord in removes) {
_sectionRecords.Remove(sectionRecord.ConfigKey);
}
}
}
// Remove all location section input defined here
if (_locationSections != null) {
_locationSections.Clear();
}
// Remove all factory records
if (_factoryRecords != null) {
_factoryRecords.Clear();
}
}
if (!implicitSectionsAdded) {
// Always add implicit sections no matter we have a file or not.
AddImplicitSections(null);
}
}
private bool IsInitDelayed {
get {
return _initDelayedRoot != null;
}
}
// RefreshFactoryRecord
//
// Refresh the Factory Record for a particular section.
//
private void RefreshFactoryRecord(string configKey) {
Hashtable factoryList = null;
FactoryRecord factoryRecord = null;
ConfigurationSchemaErrors errors;
errors = new ConfigurationSchemaErrors();
// Get Updated Factory List from File
int lineNumber = 0;
try {
using (Impersonate()) {
using (Stream stream = Host.OpenStreamForRead(ConfigStreamInfo.StreamName)) {
if (stream != null) {
ConfigStreamInfo.HasStream = true;
using (XmlUtil xmlUtil = new XmlUtil(stream, ConfigStreamInfo.StreamName, true, errors)) {
try {
factoryList = ScanFactories(xmlUtil);
ThrowIfParseErrors(xmlUtil.SchemaErrors);
}
catch {
lineNumber = xmlUtil.LineNumber;
throw;
}
}
}
}
}
// Add implicit sections to the factory list
if (factoryList == null) {
// But if factoryList isn't found in this config, we still try to
// add implicit sections to an empty factoryList.
factoryList = new Hashtable();
}
AddImplicitSections(factoryList);
if (factoryList != null) {
factoryRecord = (FactoryRecord) factoryList[configKey];
}
}
//
// Guarantee that exceptions contain the name of the stream and an approximate line number if available.
//
// And don't allow frames up the stack to run exception filters while impersonated.
catch (Exception e) {
errors.AddError(
ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_error_loading_XML_file), e, ConfigStreamInfo.StreamName, lineNumber),
ExceptionAction.Global);
}
// Set/Add/Remove Record
// Note that the _factoryRecords hashtable is protected by the hierarchy lock.
if (factoryRecord != null || HasFactoryRecords) {
EnsureFactories()[configKey] = factoryRecord;
}
// Throw accumulated errors
ThrowIfParseErrors(errors);
}
internal IInternalConfigHost Host {
get {return _configRoot.Host;}
}
internal IInternalConfigurationBuilderHost ConfigBuilderHost {
get { return _configRoot.ConfigBuilderHost; }
}
internal BaseConfigurationRecord Parent {
get {return _parent;}
}
internal bool IsRootConfig {
get {return _parent == null;}
}
internal bool IsMachineConfig {
get {return _parent == _configRoot.RootConfigRecord;}
}
internal string LocationSubPath {
get {return _locationSubPath;}
}
internal bool IsLocationConfig {
get {return _locationSubPath != null;}
}
protected ConfigRecordStreamInfo ConfigStreamInfo {
get {
if (IsLocationConfig) {
return _parent._configStreamInfo;
}
else {
return _configStreamInfo;
}
}
}
private object GetSection(string configKey, bool getLkg, bool checkPermission) {
object result;
object resultRuntimeObject;
//
// Note that GetSectionRecursive may invalidate this record,
// so there should be no further references to 'this' after the call.
//
GetSectionRecursive(
configKey, getLkg, checkPermission, true /* getRuntimeObject */, true /* requestIsHere */,
out result, out resultRuntimeObject);
return resultRuntimeObject;
}
private void GetSectionRecursive(
string configKey, bool getLkg, bool checkPermission, bool getRuntimeObject, bool requestIsHere,
out object result, out object resultRuntimeObject) {
result = null;
resultRuntimeObject = null;
#if DBG
Debug.Assert(requestIsHere || !checkPermission, "requestIsHere || !checkPermission");
if (getLkg) {
Debug.Assert(getRuntimeObject == true, "getRuntimeObject == true");
Debug.Assert(requestIsHere == true, "requestIsHere == true");
}
#endif
//
// Store results in temporary variables, because we don't want to return
// results if an exception is thrown by CheckPermissionAllowed.
//
object tmpResult = null;
object tmpResultRuntimeObject = null;
bool requirePermission = true;
bool isResultTrustedWithoutAptca = true;
// Throw errors from initial parse, if any.
if (!getLkg) {
ThrowIfInitErrors();
}
//
// check for a cached result
//
bool hasResult = false;
SectionRecord sectionRecord = GetSectionRecord(configKey, getLkg);
if (sectionRecord != null && sectionRecord.HasResult) {
// Results should never be stored if the section has errors.
Debug.Assert(!sectionRecord.HasErrors, "!sectionRecord.HasErrors");
// Create the runtime object if requested and does not yet exist.
if (getRuntimeObject && !sectionRecord.HasResultRuntimeObject) {
try {
sectionRecord.ResultRuntimeObject = GetRuntimeObject(sectionRecord.Result);
}
catch {
//
// Ignore the error if we are attempting to retreive
// the last known good configuration.
//
if (!getLkg) {
throw;
}
}
}
// Get the cached result.
if (!getRuntimeObject || sectionRecord.HasResultRuntimeObject) {
requirePermission = sectionRecord.RequirePermission;
isResultTrustedWithoutAptca = sectionRecord.IsResultTrustedWithoutAptca;
tmpResult = sectionRecord.Result;
if (getRuntimeObject) {
tmpResultRuntimeObject = sectionRecord.ResultRuntimeObject;
}
hasResult = true;
}
}
//
// If there is no cached result, get the parent's section,
// then merge it with our own input if we have any.
//
if (!hasResult) {
FactoryRecord factoryRecord = null;
bool hasInput = (sectionRecord != null && sectionRecord.HasInput);
//
// We want to cache results in a section record if:
// - The request is made at this level, and so is likely to be
// made here again.
// OR
// - The section has input, in which case we want to
// avoid evaluating the same input multiple times.
//
bool cacheResults = (requestIsHere || hasInput);
bool isRootDeclaration;
try {
//
// We need to get a factory record to:
// - Check whether the caller has permission to access a section.
// - Determine if this is the root declaration of a config section,
// and thus the termination point for recursion.
// - Get a factory that can create a configuration section.
//
// Since most factories will be declared in machine.config and not in
// child config files, we do not optimize for checking whether a
// factory record is the root declaration, as the calculation at
// machine.config is trivial.
//
// It WILL be common in web scenarios for there to be
// deep hierarchies of config files, most of which have
// sparse input. Therefore we do not want to retreive a
// factory record if it is not necessary to do so, as
// it would always lead to an order N-squared operation,
// where N is the depth of the config hierarchy.
//
// We can skip the reteival of a factory record if:
// - This is the recursive call to GetSectionRecursive,
// AND
// - There is no section input at this level,
// AND
// - No factory is declared at this level.
//
// In this case, we'll simply continue the recursion to our parent.
//
if (requestIsHere) {
//
// Ensure that we have a valid factory record and a valid factory
// for creating sections when a request for a section is first
// made.
//
factoryRecord = FindAndEnsureFactoryRecord(configKey, out isRootDeclaration);
//
// If initialization is delayed, complete initialization if:
// - We can't find the requested factory, and it therefore
// may be in the file we haven't yet read,
// OR
// - The definition of that factory is allowed at
// levels of the config hierarchy that have not
// been initialized.
//
// This works for client config scenarios because the default
// for AllowExeDefinition is MachineToApplication. It would not
// be useful for Web scenarios, as most sections can be requested
// Everywhere.
//
// Note that configuration errors that may be present in the
// file where intialization is delayed will be ignored, and
// thus the order in which configuration sections are requested
// will affect results. This is considered OK as it is very
// expensive to determine configuration paths to
// client user configuration files, which aren't needed by
// most applications.
//
if ( IsInitDelayed
&& ( factoryRecord == null
|| _initDelayedRoot.IsDefinitionAllowed(factoryRecord.AllowDefinition, factoryRecord.AllowExeDefinition))) {
if (factoryRecord == null) {
//
// No factory was found in machine or exe config, and
// initialization of the user config levels (roaming and local)
// has been delayed.
//
// As described above, the design in this case is to complete
// initialization of the user config levels now, and then
// search them to see if they contain a matching factory.
//
// Unfortunately situations have emerged where initializing the
// user config levels at this point would introduce significant
// appcompat breaks. In such cases, ignore the user config
// levels and instead return immediately to simulate the
// case where the user config file contains no matching factory.
//
// NOTE: If the factory is present in the user config files,
// callers can observe inconsistent results. Initially, the
// factory will be ignored. Once any unrelated operation forces
// user config initialization to occur, the factory will be
// used and the associated content will be surfaced to the
// caller. While undesirable, this inconsistency is being
// allowed since the relevant factories are expected to rarely
// (if ever) appear in user config files, and addressing the
// inconsistency (e.g., by updating ScanFactoriesRecursive to
// ignore any releveant factories found at user config levels)
// would significantly expand the footprint of this targeted
// appcompat shim.
//
// NOTE: This logic does NOT ignore user config files in
// any case where a factory was found in the machine or exe
// config and contains an attribute (e.g.,
// allowExeDefinition="MachineToLocalUser") which explicitly
// says that user config files are allowed to contribute
// content.
//
if (NeverLoadUserConfigFilesDuringFactorySearch(configKey)) {
return;
}
}
//
// We are going to remove this record, so get any data we need
// before the reference to 'this' becomes invalid.
//
string configPath = this._configPath;
InternalConfigRoot configRoot = this._configRoot;
// Tell the host to no longer permit delayed initialization.
Host.RequireCompleteInit(_initDelayedRoot);
// Removed config at the root of where initialization is delayed.
_initDelayedRoot.Remove();
// Get the config record for this config path
BaseConfigurationRecord newRecord = (BaseConfigurationRecord) configRoot.GetConfigRecord(configPath);
// Repeat the call to GetSectionRecursive
newRecord.GetSectionRecursive(
configKey, getLkg, checkPermission,
getRuntimeObject, requestIsHere,
out result, out resultRuntimeObject);
// Return and make no more references to this record.
return;
}
//
// For compatibility with previous versions,
// return null if the section is not found
// or is a group.
//
if (factoryRecord == null || factoryRecord.IsGroup) {
return;
}
//
// Use the factory record's copy of the configKey,
// so that we don't store more than one instance
// of the same configKey.
//
configKey = factoryRecord.ConfigKey;
}
else if (hasInput) {
//
// We'll need a factory to evaluate the input.
//
factoryRecord = FindAndEnsureFactoryRecord(configKey, out isRootDeclaration);
Debug.Assert(factoryRecord != null, "factoryRecord != null");
}
else {
//
// We don't need a factory record unless this is the root declaration.
// We know it is not the root declaration if there is no factory
// declared here. This is important to avoid a walk up the config
// hierachy when there is no input in this record.
//
factoryRecord = GetFactoryRecord(configKey, false);
if (factoryRecord == null) {
isRootDeclaration = false;
}
else {
factoryRecord = FindAndEnsureFactoryRecord(configKey, out isRootDeclaration);
Debug.Assert(factoryRecord != null, "factoryRecord != null");
}
}
// We need a factory record to check permission.
Debug.Assert(!checkPermission || factoryRecord != null, "!checkPermission || factoryRecord != null");
//
// If this is the root declaration, then we always want to cache
// the result, in order to prevent the section default from being
// created multiple times.
//
if (isRootDeclaration) {
cacheResults = true;
}
//
// We'll need a section record to cache results,
// and maybe to use in creating the section default.
//
if (sectionRecord == null && cacheResults) {
sectionRecord = EnsureSectionRecord(configKey, true);
}
//
// Retrieve the parent's runtime object if the runtimeObject
// is requested, and we are not going to merge that input
// with input in this section.
//
bool getParentRuntimeObject = (getRuntimeObject && !hasInput);
object parentResult = null;
object parentResultRuntimeObject = null;
if (isRootDeclaration) {
//
// Create the default section.
//
// Use the existing section record to create it if there is no input,
// so that the cached result is attached to the correct record.
//
SectionRecord sectionRecordForDefault = (hasInput) ? null : sectionRecord;
CreateSectionDefault(configKey, getParentRuntimeObject, factoryRecord, sectionRecordForDefault,
out parentResult, out parentResultRuntimeObject);
}
else {
//
// Get the parent section.
//
_parent.GetSectionRecursive(
configKey, false /* getLkg */, false /* checkPermission */,
getParentRuntimeObject, false /* requestIsHere */,
out parentResult, out parentResultRuntimeObject);
}
if (hasInput) {
//
// Evaluate the input.
//
// If Evaluate() encounters an error, it may not throw an exception
// when getLkg == true.
//
// The complete success of the evaluation is determined by the return value.
//
bool success = Evaluate(factoryRecord, sectionRecord, parentResult, getLkg, getRuntimeObject,
out tmpResult, out tmpResultRuntimeObject);
Debug.Assert(success || getLkg, "success || getLkg");
if (!success) {
Debug.Assert(getLkg == true, "getLkg == true");
// Do not cache partial results if getLkg was specified.
cacheResults = false;
}
}
else {
//
// If we are going to cache results here, we will need
// to create a copy in the case of MgmtConfigurationRecord -
// otherwise we could inadvertently return the parent to the user,
// which could then be modified.
//
if (sectionRecord != null) {
tmpResult = UseParentResult(configKey, parentResult, sectionRecord);
if (getRuntimeObject) {
//
// If the parent result is the same as the parent runtime object,
// then use the same copy of the parent result for our own runtime object.
//
if (object.ReferenceEquals(parentResult, parentResultRuntimeObject)) {
tmpResultRuntimeObject = tmpResult;
}
else {
tmpResultRuntimeObject = UseParentResult(configKey, parentResultRuntimeObject, sectionRecord);
}
}
}
else {
Debug.Assert(!requestIsHere, "!requestIsHere");
//
// We don't need to make a copy if we are not storing
// the result, and thus not returning the result to the
// caller of GetSection.
//
tmpResult = parentResult;
tmpResultRuntimeObject = parentResultRuntimeObject;
}
}
//
// Determine which permissions are required of the caller.
//
if (cacheResults || checkPermission) {
requirePermission = factoryRecord.RequirePermission;
isResultTrustedWithoutAptca = factoryRecord.IsFactoryTrustedWithoutAptca;
//
// Cache the results.
//
if (cacheResults) {
if (sectionRecord == null) {
sectionRecord = EnsureSectionRecord(configKey, true);
}
sectionRecord.Result = tmpResult;
if (getRuntimeObject) {
sectionRecord.ResultRuntimeObject = tmpResultRuntimeObject;
}
sectionRecord.RequirePermission = requirePermission;
sectionRecord.IsResultTrustedWithoutAptca = isResultTrustedWithoutAptca;
}
}
hasResult = true;
}
catch {
//
// Ignore the error if we are attempting to retreive
// the last known good configuration.
//
if (!getLkg) {
throw;
}
}
//
// If we don't have a result, ask our parent for its
// last known good result.
//
if (!hasResult) {
Debug.Assert(getLkg == true, "getLkg == true");
_parent.GetSectionRecursive(
configKey, true /* getLkg */, checkPermission,
true /* getRuntimeObject */, true /* requestIsHere */,
out result, out resultRuntimeObject);
return;
}
}
//
// Check if permission to access the section is allowed.
//
if (checkPermission) {
CheckPermissionAllowed(configKey, requirePermission, isResultTrustedWithoutAptca);
}
//
// Return the results.
//
result = tmpResult;
if (getRuntimeObject) {
resultRuntimeObject = tmpResultRuntimeObject;
}
}
//
// Note that the "configKey" is generally the section name passed to a GetSection API and
// is therefore case-sensitive.
//
private static bool NeverLoadUserConfigFilesDuringFactorySearch(string configKey) {
if (!LocalAppContextSwitches.AllowUserConfigFilesToLoadWhenSearchingForWellKnownSqlClientFactories) {
//
// These two sections control new System.Data.SqlClient features that
// were added in .NET 4.7.2. The SqlClient code queries these sections
// on demand by calling ConfigurationManager.GetSection from class
// constructors triggered during first use of the SqlClient services.
//
// For many customers, this broke appcompat because:
// - The first SqlClient use happened at point where customer code
// had attached non-serializable values to the logical call context.
// - The new section is not present in the machine or exe config files,
// so the first ConfigurationManager.GetSection loads the user
// config files on demand.
// - An AppDomain.CurrentDomain.Evidence call always occurs as part
// of computing the user config file paths.
// - In most secondary app domains, loading the evidence calls over
// into the default domain, which throws an unhandled
// SerializationException due to the non-serializable call context.
//
// Investigation showed that new SqlClient ConfigurationManager.GetSection
// calls cannot be removed without regressing the new .NET 4.7.2 features
// (which have shipped and are in wide use). That said, broad and heavy
// impact across large-scale customer scenarios means that some kind of
// appcompat mitigation needs to ship via servicing.
//
// The consensus is that it is very unlikely that customers are using
// user config files to configure the new SqlClient features. As a result,
// the customer impact is being mitigated via a targeted appcompat shim
// which prevents the user config files from ever being loaded during
// ConfigurationManager.GetSection calls that target any of the new
// sections.
//
if ((configKey == "SqlColumnEncryptionEnclaveProviders") ||
(configKey == "SqlAuthenticationProviders")) {
return true;
}
}
return false;
}
protected void CreateSectionDefault(
string configKey, bool getRuntimeObject, FactoryRecord factoryRecord, SectionRecord sectionRecord,
out object result, out object resultRuntimeObject) {
result = null;
resultRuntimeObject = null;
SectionRecord sectionRecordForDefault;
if (sectionRecord != null) {
sectionRecordForDefault = sectionRecord;
}
else {
sectionRecordForDefault = new SectionRecord(configKey);
}
object tmpResult = CallCreateSection(true, factoryRecord, sectionRecordForDefault, null, null, null);
object tmpResultRuntimeObject;
if (getRuntimeObject) {
tmpResultRuntimeObject = GetRuntimeObject(tmpResult);
}
else {
tmpResultRuntimeObject = null;
}
result = tmpResult;
resultRuntimeObject = tmpResultRuntimeObject;
}
#if UNUSED_CODE
//
// Create a section that is used to manipulate a section that has errors.
//
private DefaultSection CreateErrorSection(
string configKey, FactoryRecord factoryRecord, SectionRecord sectionRecord, SectionInput input, Exception e) {
//
// We make assumptions in this implemntation that CreateErrorSection is called
// only for MgmtConfigurationRecord.
//
Debug.Assert(this is MgmtConfigurationRecord, "this is MgmtConfigurationRecord");
string group;
string name;
string factoryTypeName;
bool allowLocation;
ConfigurationAllowDefinition allowDefinition;
ConfigurationAllowExeDefinition allowExeDefinition;
bool restartOnExternalChanges;
bool requirePermission;
bool isFromTrustedConfigRecord;
string factoryFilename;
int factoryLineNumber;
// The original factory record will contain either valid values,
// or the default values in places where there was an error.
if (factoryRecord != null) {
configKey = factoryRecord.ConfigKey;
group = factoryRecord.Group;
name = factoryRecord.Name;
factoryTypeName = factoryRecord.FactoryTypeName;
allowLocation = factoryRecord.AllowLocation;
allowDefinition = factoryRecord.AllowDefinition;
allowExeDefinition = factoryRecord.AllowExeDefinition;
restartOnExternalChanges = factoryRecord.RestartOnExternalChanges;
requirePermission = factoryRecord.RequirePermission;
isFromTrustedConfigRecord = factoryRecord.IsFromTrustedConfigRecord;
factoryFilename = factoryRecord.Filename;
factoryLineNumber = factoryRecord.LineNumber;
}
else {
SplitConfigKey(configKey, out group, out name);
factoryTypeName = typeof(DefaultSection).AssemblyQualifiedName;
allowLocation = true;
allowDefinition = ConfigurationAllowDefinition.Everywhere;
allowExeDefinition = ConfigurationAllowExeDefinition.MachineToApplication;
restartOnExternalChanges = true;
requirePermission = true;
isFromTrustedConfigRecord = false;
factoryFilename = null;
factoryLineNumber = -1;
}
// create a new factory record
FactoryRecord factoryRecordForCreate = new FactoryRecord(
configKey,
group,
name,
factoryTypeName,
allowLocation,
allowDefinition,
allowExeDefinition,
restartOnExternalChanges,
requirePermission,
isFromTrustedConfigRecord,
factoryFilename,
factoryLineNumber);
//
// Create the factory needed to create the section.
//
Debug.Assert(this is MgmtConfigurationRecord, "this is MgmtConfigurationRecord");
ConstructorInfo ctor = TypeUtil.GetConstructorWithReflectionPermission(typeof(DefaultSection), typeof(ConfigurationSection), true);
factoryRecordForCreate.Factory = ctor;
factoryRecordForCreate.IsFactoryTrustedWithoutAptca = false;
//
// Try to get the section content as raw xml.
//
ConfigXmlReader configXmlReader = null;
string filename = null;
int lineNumber = -1;
if (input != null) {
filename = input.SectionXmlInfo.Filename;
lineNumber = input.SectionXmlInfo.LineNumber;
string [] keys = sectionRecord.ConfigKey.Split(ConfigPathSeparatorParams);
try {
configXmlReader = GetSectionXmlReader(keys, input);
}
catch {
}
}
DefaultSection section = (DefaultSection) CallCreateSection(
isFromTrustedConfigRecord, factoryRecordForCreate, sectionRecord,
null /* parentConfig */, configXmlReader, filename, lineNumber);
section.ElementInformation.SetError(configKey, e);
return section;
}
#endif
//
// Check if an input should be skipped based on inheritInChildApplications setting.
//
// If skipInChildApps == true (it means inheritInChildApplications=="false" in the location tag):
//
// - If _flags[IsAboveApplication]==true, that means the app pointed to by _configPath is above of the
// current running app. In another word, the running app is a child app of the app pointed to by _configPath.
// In this case, we should skip the input.
//
// - If _flags[IsAboveApplication]==false, that means the app pointed to by _configPath == current running app.
// In this case it's okay to use the input.
//
private bool ShouldSkipDueToInheritInChildApplications(bool skipInChildApps) {
return (skipInChildApps && _flags[IsAboveApplication]);
}
// configPath - The config path of the config record to which a location tag points to.
private bool ShouldSkipDueToInheritInChildApplications(bool skipInChildApps, string configPath) {
return (skipInChildApps && Host.IsAboveApplication(configPath));
}
//
//
// Evaluate the input.
//
// If Evaluate() encounters an error, it may not throw an exception
// when getLkg == true.
//
// The complete success of the evaluation is determined by the return value.
//
private bool Evaluate(
FactoryRecord factoryRecord, SectionRecord sectionRecord, object parentResult,
bool getLkg, bool getRuntimeObject, out object result, out object resultRuntimeObject) {
result = null;
resultRuntimeObject = null;
//
// Store results in temporary variables, because we don't want to return
// results if an exception is thrown.
//
object tmpResult = null;
object tmpResultRuntimeObject = null;
// Factory record should be error-free.
Debug.Assert(!factoryRecord.HasErrors, "!factoryRecord.HasErrors");
// We should have some input
Debug.Assert(sectionRecord.HasInput, "sectionRecord.HasInput");
//
// Grab inputs before checking result,
// as inputs may be cleared once the result is set.
//
List<SectionInput> locationInputs = sectionRecord.LocationInputs;
List<SectionInput> indirectLocationInputs = sectionRecord.IndirectLocationInputs;
SectionInput fileInput = sectionRecord.FileInput;
Debug.Assert(!(IsLocationConfig && getLkg),
"Don't expect getLkg to be true when we're dealing with a location config record");
//
// Now that we have our inputs, lets check if there
// is a result, because if there is, our inputs might
// have been invalidated.
//
bool success = false;
if (sectionRecord.HasResult) {
// Results should never be stored if the section has errors.
Debug.Assert(!sectionRecord.HasErrors, "!sectionRecord.HasErrors");
// Create the runtime object if requested and it does not yet exist.
if (getRuntimeObject && !sectionRecord.HasResultRuntimeObject) {
try {
sectionRecord.ResultRuntimeObject = GetRuntimeObject(sectionRecord.Result);
}
catch {
//
// Ignore the error if we are attempting to retreive
// the last known good configuration.
//
if (!getLkg) {
throw;
}
}
}
// Get the cached result.
if (!getRuntimeObject || sectionRecord.HasResultRuntimeObject) {
tmpResult = sectionRecord.Result;
if (getRuntimeObject) {
tmpResultRuntimeObject = sectionRecord.ResultRuntimeObject;
}
success = true;
}
}
if (!success) {
Exception savedException = null;
try {
string configKey = factoryRecord.ConfigKey;
string [] keys = configKey.Split(ConfigPathSeparatorParams);
object currentResult = parentResult;
//
// Evaluate indirectLocationInputs. Used only in location config record.
// See the comment for VSWhidbey 540184 in Init() for more details.
//
if (indirectLocationInputs != null) {
Debug.Assert(IsLocationConfig, "indirectLocationInputs is non-null only in location config record");
foreach (SectionInput input in indirectLocationInputs) {
if (!input.HasResult) {
input.ThrowOnErrors();
bool isTrusted = Host.IsTrustedConfigPath(input.SectionXmlInfo.DefinitionConfigPath);
input.Result = EvaluateOne(keys, input, isTrusted, factoryRecord, sectionRecord, currentResult);
}
currentResult = input.Result;
}
}
//
// Evaluate location inputs
//
if (locationInputs != null) {
foreach (SectionInput locationInput in locationInputs) {
if (!locationInput.HasResult) {
locationInput.ThrowOnErrors();
bool isTrusted = Host.IsTrustedConfigPath(locationInput.SectionXmlInfo.DefinitionConfigPath);
locationInput.Result = EvaluateOne(keys, locationInput, isTrusted, factoryRecord, sectionRecord, currentResult);
}
currentResult = locationInput.Result;
}
}
//
// Evaluate file input
//
if (fileInput != null) {
if (!fileInput.HasResult) {
fileInput.ThrowOnErrors();
bool isTrusted = _flags[IsTrusted];
fileInput.Result = EvaluateOne(keys, fileInput, isTrusted, factoryRecord, sectionRecord, currentResult);
}
currentResult = fileInput.Result;
}
else {
//
// The section needs its own copy of the result that is distinct
// from its location parent result.
//
// Please note that this is needed only in design-time because any
// change to the section shouldn't go to the locationInput.Result.
//
// In runtime, UseParentResult does nothing.
//
Debug.Assert(locationInputs != null || indirectLocationInputs != null,
"locationInputs != null || indirectLocationInputs != null");
currentResult = UseParentResult(configKey, currentResult, sectionRecord);
}
if (getRuntimeObject) {
tmpResultRuntimeObject = GetRuntimeObject(currentResult);
}
tmpResult = currentResult;
success = true;
}
catch (Exception e) {
//
// Catch the exception if LKG is requested and we have
// location input to fall back on.
//
if (getLkg && locationInputs != null) {
savedException = e;
}
else {
throw;
}
}
//
// If getLkg, then return a result from the last valid location input.
//
if (!success) {
Debug.Assert(getLkg == true, "getLkg == true");
int i = locationInputs.Count;
while (--i >= 0) {
SectionInput locationInput = locationInputs[i];
if (locationInput.HasResult) {
if (getRuntimeObject && !locationInput.HasResultRuntimeObject) {
try {
locationInput.ResultRuntimeObject = GetRuntimeObject(locationInput.Result);
}
catch {
}
}
if (!getRuntimeObject || locationInput.HasResultRuntimeObject) {
tmpResult = locationInput.Result;
if (getRuntimeObject) {
tmpResultRuntimeObject = locationInput.ResultRuntimeObject;
}
break;
}
}
}
if (i < 0) {
throw savedException;
}
}
}
//
// If evaluation was successful, we can remove any saved rawXml.
//
if (success && !_flags[SupportsKeepInputs]) {
sectionRecord.ClearRawXml();
}
result = tmpResult;
if (getRuntimeObject) {
resultRuntimeObject = tmpResultRuntimeObject;
}
return success;
}
private object EvaluateOne(
string[] keys, SectionInput input, bool isTrusted,
FactoryRecord factoryRecord, SectionRecord sectionRecord, object parentResult) {
object result;
try {
ConfigXmlReader reader = GetSectionXmlReader(keys, input);
if (reader == null) {
//
// If section is not found in a file, use the parent result
//
result = UseParentResult(factoryRecord.ConfigKey, parentResult, sectionRecord);
}
else {
result = CallCreateSection(isTrusted, factoryRecord, sectionRecord, input, parentResult, reader);
}
}
catch (Exception e) {
throw ExceptionUtil.WrapAsConfigException(
SR.GetString(SR.Config_exception_creating_section, factoryRecord.ConfigKey),
e, input.SectionXmlInfo);
}
return result;
}
//
// Create a single cached instance of UnrestrictedConfigPermission.
//
private static ConfigurationPermission UnrestrictedConfigPermission {
get {
if (s_unrestrictedConfigPermission == null) {
s_unrestrictedConfigPermission = new ConfigurationPermission(PermissionState.Unrestricted);
}
return s_unrestrictedConfigPermission;
}
}
//
// Check whether permission to the section is allowed to the caller.
//
private void CheckPermissionAllowed(string configKey, bool requirePermission, bool isTrustedWithoutAptca) {
//
// Demand unrestricted ConfigurationPermission if the section requires it
//
if (requirePermission) {
try {
UnrestrictedConfigPermission.Demand();
}
catch (SecurityException e) {
//
// Add a nice error message that includes the sectionName and explains
// how to use the requirePermission attribute.
//
throw new SecurityException(
SR.GetString(SR.ConfigurationPermission_Denied, configKey),
e);
}
}
//
// Ensure that the recepient isn't receiving an object they otherwise
// wouldn't be able to create due to Aptca.
//
if (isTrustedWithoutAptca && !Host.IsFullTrustSectionWithoutAptcaAllowed(this)) {
throw new ConfigurationErrorsException(SR.GetString(SR.Section_from_untrusted_assembly, configKey));
}
}
private ConfigXmlReader FindSection(string [] keys, SectionXmlInfo sectionXmlInfo, out int lineNumber) {
lineNumber = 0;
ConfigXmlReader section = null;
try {
using (Impersonate()) {
using (Stream stream = Host.OpenStreamForRead(sectionXmlInfo.Filename)) {
if ( !_flags[SupportsRefresh]
&& (stream == null || HasStreamChanged(sectionXmlInfo.Filename, sectionXmlInfo.StreamVersion))) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_file_has_changed), sectionXmlInfo.Filename, 0);
}
if (stream != null) {
//
using (XmlUtil xmlUtil = new XmlUtil(stream, sectionXmlInfo.Filename, true)) {
if (sectionXmlInfo.SubPath == null) {
section = FindSectionRecursive(keys, 0, xmlUtil, ref lineNumber);
}
else {
// search children of <configuration> for <location>
xmlUtil.ReadToNextElement();
while (xmlUtil.Reader.Depth > 0) {
if (xmlUtil.Reader.Name == KEYWORD_LOCATION) {
bool locationValid = false;
string locationSubPathAttribute = xmlUtil.Reader.GetAttribute(KEYWORD_LOCATION_PATH);
try {
locationSubPathAttribute = NormalizeLocationSubPath(locationSubPathAttribute, xmlUtil);
locationValid = true;
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.NonSpecific);
}
if (locationValid &&
StringUtil.EqualsIgnoreCase(sectionXmlInfo.SubPath, locationSubPathAttribute)) {
section = FindSectionRecursive(keys, 0, xmlUtil, ref lineNumber);
if (section != null)
break;
}
}
xmlUtil.SkipToNextElement();
}
}
// Throw accumulated errors
ThrowIfParseErrors(xmlUtil.SchemaErrors);
}
}
}
}
}
// Don't allow frames up the stack to run exception filters while impersonated.
catch {
throw;
}
return section;
}
[SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification="Reading trusted input")]
private ConfigXmlReader FindSectionRecursive(string [] keys, int iKey, XmlUtil xmlUtil, ref int lineNumber) {
string name = keys[iKey];
ConfigXmlReader section = null;
int depth = xmlUtil.Reader.Depth;
xmlUtil.ReadToNextElement();
while (xmlUtil.Reader.Depth > depth) {
if (xmlUtil.Reader.Name == name) {
if (iKey < keys.Length - 1) {
//
// We haven't reached the section yet, so keep evaluating
//
section = FindSectionRecursive(keys, iKey + 1, xmlUtil, ref lineNumber);
if (section != null) {
break;
}
continue; // don't call "Skip" -- FindSectionRecursive forwards the reader
}
else {
//
// We've reached the section. Load the section into a string.
//
string filename = ((IConfigErrorInfo)xmlUtil).Filename;
int lineOffset = xmlUtil.Reader.LineNumber;
string rawXml = xmlUtil.CopySection();
section = new ConfigXmlReader(rawXml, filename, lineOffset);
break;
}
}
else if (iKey == 0 && xmlUtil.Reader.Name == KEYWORD_LOCATION) {
string locationSubPath = xmlUtil.Reader.GetAttribute(KEYWORD_LOCATION_PATH);
bool isValid = false;
try {
locationSubPath = NormalizeLocationSubPath(locationSubPath, xmlUtil);
isValid = true;
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.NonSpecific);
}
if (isValid && locationSubPath == null) {
//
// Location sections that don't have a subpath are treated
// as ordinary sections.
//
section = FindSectionRecursive(keys, iKey, xmlUtil, ref lineNumber);
if (section != null) {
break;
}
continue; // don't call "Skip" -- FindSectionRecursive forwards the reader
}
}
xmlUtil.SkipToNextElement();
}
return section;
}
[SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification="Reading trusted input")]
private ConfigXmlReader LoadConfigSource(string name, SectionXmlInfo sectionXmlInfo) {
string configSourceStreamName = sectionXmlInfo.ConfigSourceStreamName;
try {
using (Impersonate()) {
using (Stream stream = Host.OpenStreamForRead(configSourceStreamName)) {
if (stream == null) {
throw new ConfigurationErrorsException(
SR.GetString(SR.Config_cannot_open_config_source, sectionXmlInfo.ConfigSource),
sectionXmlInfo);
}
using (XmlUtil xmlUtil = new XmlUtil(stream, configSourceStreamName, true)) {
if (xmlUtil.Reader.Name != name) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_source_file_format), xmlUtil);
}
// Check for protectionProvider
string protectionProviderAttribute = xmlUtil.Reader.GetAttribute(KEYWORD_PROTECTION_PROVIDER);
if (protectionProviderAttribute != null) {
if (xmlUtil.Reader.AttributeCount != 1) {
// Error: elements with protectionProvider should not have other attributes
throw new ConfigurationErrorsException(SR.GetString(SR.Protection_provider_syntax_error), xmlUtil);
}
sectionXmlInfo.ProtectionProviderName = ValidateProtectionProviderAttribute(protectionProviderAttribute, xmlUtil);
}
// Check for configBuilder
string configBuilderAttribute = xmlUtil.Reader.GetAttribute(KEYWORD_CONFIG_BUILDER);
if (configBuilderAttribute != null) {
sectionXmlInfo.ConfigBuilderName = ValidateConfigBuilderAttribute(configBuilderAttribute, xmlUtil);
}
int lineOffset = xmlUtil.Reader.LineNumber;
string rawXml = xmlUtil.CopySection();
// Detect if there is any XML left over after the section
while (!xmlUtil.Reader.EOF) {
XmlNodeType t = xmlUtil.Reader.NodeType;
if (t != XmlNodeType.Comment) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_source_file_format), xmlUtil);
}
xmlUtil.Reader.Read();
}
ConfigXmlReader section = new ConfigXmlReader(rawXml, configSourceStreamName, lineOffset);
return section;
}
}
}
}
catch {
// Don't allow frames up the stack to run exception filters while impersonated.
throw;
}
}
[SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification="Reading trusted input")]
protected ConfigXmlReader GetSectionXmlReader(string[] keys, SectionInput input) {
ConfigXmlReader reader = null;
string filename = input.SectionXmlInfo.Filename;
int lineNumber = input.SectionXmlInfo.LineNumber;
try {
string name = keys[keys.Length-1];
string rawXml = input.SectionXmlInfo.RawXml;
if (rawXml != null) {
// Use the stored raw xml to provide the content of the section.
reader = new ConfigXmlReader(rawXml, input.SectionXmlInfo.Filename, input.SectionXmlInfo.LineNumber);
}
else if (!String.IsNullOrEmpty(input.SectionXmlInfo.ConfigSource)) {
// Load the config source to provide the content of the section.
filename = input.SectionXmlInfo.ConfigSourceStreamName;
lineNumber = 0;
reader = LoadConfigSource(name, input.SectionXmlInfo);
}
else {
// Find the content of the section in the config file.
lineNumber = 0;
reader = FindSection(keys, input.SectionXmlInfo, out lineNumber);
}
// Decrypt protected sections
if (reader != null) {
if (!input.IsProtectionProviderDetermined) {
input.ProtectionProvider = GetProtectionProviderFromName(input.SectionXmlInfo.ProtectionProviderName, false);
}
if (input.ProtectionProvider != null) {
reader = DecryptConfigSection(reader, input.ProtectionProvider);
}
}
// Allow configBuilder a chance to modify
if (reader != null) {
if (!input.IsConfigBuilderDetermined && !String.IsNullOrWhiteSpace(input.SectionXmlInfo.ConfigBuilderName)) {
input.ConfigBuilder = GetConfigBuilderFromName(input.SectionXmlInfo.ConfigBuilderName);
}
if (input.IsConfigBuilderDetermined) {
// Decrypt removes the invalid "configProtectionProvider" attribute simply by returning an xml reader for the
// decrypted inner xml... which persumably does not have the attribute. ConfigBuilders OTOH, will presumably
// return an xml reader based on this whole section as is. And the "configBuilders" attribute will be an
// "unrecognized attribute" further down the road. So let's remove it here.
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(reader);
doc.DocumentElement.RemoveAttribute(KEYWORD_CONFIG_BUILDER);
reader = new ConfigXmlReader(doc.DocumentElement.OuterXml, filename, lineNumber);
}
if (input.ConfigBuilder != null) {
reader = ProcessRawXml(reader, input.ConfigBuilder);
}
}
}
//
// Guarantee that exceptions contain the name of the stream and an approximate line number.
//
catch (Exception e) {
throw ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_error_loading_XML_file), e, filename, lineNumber);
}
return reader;
}
internal string DefaultProviderName {
get {
return ProtectedConfig.DefaultProvider;
}
}
internal ProtectedConfigurationProvider GetProtectionProviderFromName(string providerName, bool throwIfNotFound) {
ProtectedConfigurationProvider provider = null;
if (String.IsNullOrEmpty(providerName)) {
if (throwIfNotFound) {
throw new ConfigurationErrorsException(SR.GetString(SR.ProtectedConfigurationProvider_not_found, providerName));
}
else {
return null;
}
}
provider = ProtectedConfig.GetProviderFromName(providerName);
return provider;
}
private ProtectedConfigurationSection ProtectedConfig {
get {
if (!_flags[ProtectedDataInitialized]) {
InitProtectedConfigurationSection();
}
return _protectedConfig;
}
}
internal void InitProtectedConfigurationSection() {
if (!_flags[ProtectedDataInitialized]) {
_protectedConfig = GetSection(BaseConfigurationRecord.RESERVED_SECTION_PROTECTED_CONFIGURATION, false, false) as ProtectedConfigurationSection;
Debug.Assert(_protectedConfig != null, "<configProtectedData> section should always be available because it's a built-in section");
_flags[ProtectedDataInitialized] = true;
}
}
internal ConfigurationBuilder GetConfigBuilderFromName(string builderName) {
if (String.IsNullOrEmpty(builderName) || ConfigBuilders == null) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_builder_not_found, builderName));
}
return ConfigBuilders.GetBuilderFromName(builderName);
}
private ConfigurationBuildersSection ConfigBuilders {
get {
if (!_flags[ConfigBuildersInitialized]) {
InitConfigBuildersSection();
}
return _configBuilders;
}
}
internal void InitConfigBuildersSection() {
if (!_flags[ConfigBuildersInitialized]) {
_configBuilders = GetSection(BaseConfigurationRecord.RESERVED_SECTION_CONFIGURATION_BUILDERS, false, false) as ConfigurationBuildersSection;
_flags[ConfigBuildersInitialized] = true;
}
}
protected object CallCreateSection(bool inputIsTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, SectionInput sectionInput, object parentConfig, ConfigXmlReader reader) {
object config;
string filename = null;
int line = -1;
if (sectionInput != null && sectionInput.SectionXmlInfo != null) {
filename = sectionInput.SectionXmlInfo.Filename;
line = sectionInput.SectionXmlInfo.LineNumber;
}
// Call into config section while impersonating process or UNC identity
// so that the section could read files from disk if needed
try {
using (Impersonate()) {
config = CreateSection(inputIsTrusted, factoryRecord, sectionRecord, sectionInput, parentConfig, reader);
if (config == null && parentConfig != null) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_object_is_null), filename, line);
}
}
}
catch (ThreadAbortException) {
throw;
}
catch (Exception e) {
throw ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_exception_creating_section_handler, factoryRecord.ConfigKey), e, filename, line);
}
return config;
}
// IsRootDeclaration
//
// Is this the Root Record of where this configKey is Declared
//
// If parent is null, or there is not a factory record above
// this one, then it is the root of where it can be declared
//
// Optionally consider whether implicit sections are to be considered rooted.
//
internal bool IsRootDeclaration(string configKey, bool implicitIsRooted) {
if (!implicitIsRooted && IsImplicitSection(configKey)) {
return false;
}
return (_parent.IsRootConfig || _parent.FindFactoryRecord(configKey, true) == null);
}
// Search the config hierarchy for a FactoryRecord.
// Note that callers should check whether the returned factory has errors.
internal FactoryRecord FindFactoryRecord(string configKey, bool permitErrors, out BaseConfigurationRecord configRecord) {
configRecord = null;
BaseConfigurationRecord tConfigRecord = this;
while (!tConfigRecord.IsRootConfig) {
FactoryRecord factoryRecord = tConfigRecord.GetFactoryRecord(configKey, permitErrors);
if (factoryRecord != null) {
#if DBG
if (IsImplicitSection(configKey) && !factoryRecord.HasErrors) {
Debug.Assert(tConfigRecord._parent.IsRootConfig,
"Implicit section should be found only at the record beneath the root (e.g. machine.config)");
}
#endif
configRecord = tConfigRecord;
return factoryRecord;
}
tConfigRecord = tConfigRecord._parent;
}
return null;
}
internal FactoryRecord FindFactoryRecord(string configKey, bool permitErrors) {
BaseConfigurationRecord dummy;
return FindFactoryRecord(configKey, permitErrors, out dummy);
}
//
// FindAndEnsureFactoryRecord:
//
// - Find the nearest factory record
// - Determine if it is the root
// - Create the factory in the root if it doesn't exist.
// - Determine if the factory type is from a global assembly without APTCA
// - Copy the factory and IsFactoryTrustedWithoutAptca bit into the child record
//
private FactoryRecord FindAndEnsureFactoryRecord(string configKey, out bool isRootDeclaredHere) {
isRootDeclaredHere = false;
BaseConfigurationRecord configRecord;
FactoryRecord factoryRecord = FindFactoryRecord(configKey, false, out configRecord);
if (factoryRecord != null && !factoryRecord.IsGroup) {
//
// Find the root declaration
//
FactoryRecord rootFactoryRecord = factoryRecord;
BaseConfigurationRecord rootConfigRecord = configRecord;
BaseConfigurationRecord currentConfigRecord = configRecord._parent;
while (!currentConfigRecord.IsRootConfig) {
BaseConfigurationRecord tempConfigRecord;
FactoryRecord tempFactoryRecord = currentConfigRecord.FindFactoryRecord(configKey, false, out tempConfigRecord);
if (tempFactoryRecord == null)
break;
rootFactoryRecord = tempFactoryRecord;
rootConfigRecord = tempConfigRecord;
// continue the search from the parent of the configRecord we found
currentConfigRecord = tempConfigRecord.Parent;
}
//
// A child factory record must be equivalent to its parent,
// so if the child has no errors, the parent must also have no errors.
//
Debug.Assert(!rootFactoryRecord.HasErrors, "!rootFactoryRecord.HasErrors");
if (rootFactoryRecord.Factory == null) {
try {
//
// Create the factory from the type string, and cache it
//
object factory = rootConfigRecord.CreateSectionFactory(rootFactoryRecord);
bool isFactoryTrustedWithoutAptca = TypeUtil.IsTypeFromTrustedAssemblyWithoutAptca(factory.GetType());
rootFactoryRecord.Factory = factory;
rootFactoryRecord.IsFactoryTrustedWithoutAptca = isFactoryTrustedWithoutAptca;
}
catch (Exception e) {
throw ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_exception_creating_section_handler, factoryRecord.ConfigKey), e, factoryRecord);
}
}
if (factoryRecord.Factory == null) {
factoryRecord.Factory = rootFactoryRecord.Factory;
factoryRecord.IsFactoryTrustedWithoutAptca = rootFactoryRecord.IsFactoryTrustedWithoutAptca;
}
isRootDeclaredHere = Object.ReferenceEquals(this, rootConfigRecord);
}
return factoryRecord;
}
private Hashtable ScanFactories(XmlUtil xmlUtil) {
Hashtable factoryList;
factoryList = new Hashtable();
if (xmlUtil.Reader.NodeType != XmlNodeType.Element || xmlUtil.Reader.Name != KEYWORD_CONFIGURATION) {
//
string safeFilename = ConfigurationErrorsException.AlwaysSafeFilename(((IConfigErrorInfo)xmlUtil).Filename);
throw new ConfigurationErrorsException(
SR.GetString(SR.Config_file_doesnt_have_root_configuration, safeFilename),
xmlUtil);
}
// Ignore xmlns attribute
while (xmlUtil.Reader.MoveToNextAttribute()) {
switch (xmlUtil.Reader.Name) {
case KEYWORD_XMLNS:
if (xmlUtil.Reader.Value == KEYWORD_CONFIGURATION_NAMESPACE) {
_flags[NamespacePresentInFile] = true;
_flags[NamespacePresentCurrent] = true;
} else {
ConfigurationErrorsException ce;
ce = new ConfigurationErrorsException(
SR.GetString(SR.Config_namespace_invalid, xmlUtil.Reader.Value, KEYWORD_CONFIGURATION_NAMESPACE),
xmlUtil);
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Global);
}
break;
default:
xmlUtil.AddErrorUnrecognizedAttribute(ExceptionAction.NonSpecific);
break;
}
}
// move to first child of <configuration>
xmlUtil.StrictReadToNextElement(ExceptionAction.NonSpecific);
if (xmlUtil.Reader.Depth == 1 && xmlUtil.Reader.Name == KEYWORD_CONFIGSECTIONS) {
xmlUtil.VerifyNoUnrecognizedAttributes(ExceptionAction.NonSpecific);
ScanFactoriesRecursive(xmlUtil, string.Empty, factoryList);
}
return factoryList;
}
// Scans the <configSections> section of a configuration file. The function is recursive
// to traverse arbitrarily nested config groups.
//
// <sectionGroup name="foo">
// <sectionGroup name="bar">
// <section name="----Section" type="..." />
// ...
//
// Note: This function valiates that the factory record has not been
// declared before in a parent record. (it does not check
// current record, which allows you to update list)
//
private void ScanFactoriesRecursive(XmlUtil xmlUtil, string parentConfigKey, Hashtable factoryList) {
// discard any accumulated local errors
xmlUtil.SchemaErrors.ResetLocalErrors();
int depth = xmlUtil.Reader.Depth;
xmlUtil.StrictReadToNextElement(ExceptionAction.NonSpecific);
while (xmlUtil.Reader.Depth == depth + 1) {
bool positionedAtNextElement = false;
switch (xmlUtil.Reader.Name) {
//
// Handle <sectionGroup name="groupName" [type="typename"] />
//
case KEYWORD_SECTIONGROUP: {
string tagName = null;
string typeName = null;
int lineNumber = xmlUtil.Reader.LineNumber;
while (xmlUtil.Reader.MoveToNextAttribute()) {
switch (xmlUtil.Reader.Name) {
case KEYWORD_SECTIONGROUP_NAME:
tagName = xmlUtil.Reader.Value;
VerifySectionName(tagName, xmlUtil, ExceptionAction.Local, false, false);
break;
case KEYWORD_SECTIONGROUP_TYPE:
xmlUtil.VerifyAndGetNonEmptyStringAttribute(ExceptionAction.Local, out typeName);
break;
default:
xmlUtil.AddErrorUnrecognizedAttribute(ExceptionAction.Local);
break;
}
}
xmlUtil.Reader.MoveToElement(); // if on an attribute move back to the element
if (!xmlUtil.VerifyRequiredAttribute(
tagName,
KEYWORD_SECTIONGROUP_NAME,
ExceptionAction.NonSpecific)) {
//
// Without a name, we cannot continue parsing the sections and groups within.
// Skip the entire section.
//
xmlUtil.SchemaErrors.RetrieveAndResetLocalErrors(true);
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
}
else {
string configKey = CombineConfigKey(parentConfigKey, tagName);
FactoryRecord factoryRecord = (FactoryRecord) factoryList[configKey];
if (factoryRecord != null) {
// Error: duplicate sectionGroup declaration
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_already_defined_at_this_level, tagName), xmlUtil),
ExceptionAction.Local);
} else {
FactoryRecord parentFactoryRecord = _parent.FindFactoryRecord(configKey, true);
if (parentFactoryRecord != null) {
configKey = parentFactoryRecord.ConfigKey;
// make sure that an ancestor has not defined a section with the same name as the group
if ( parentFactoryRecord != null &&
(!parentFactoryRecord.IsGroup ||
!parentFactoryRecord.IsEquivalentSectionGroupFactory(Host, typeName))) {
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_already_defined, tagName), xmlUtil),
ExceptionAction.Local);
parentFactoryRecord = null;
}
}
if (parentFactoryRecord != null) {
factoryRecord = parentFactoryRecord.CloneSectionGroup(typeName, xmlUtil.Filename, lineNumber);
}
else {
factoryRecord = new FactoryRecord(configKey, parentConfigKey, tagName, typeName, xmlUtil.Filename, lineNumber);
}
factoryList[configKey] = factoryRecord;
}
// Add any errors we may have encountered
factoryRecord.AddErrors(xmlUtil.SchemaErrors.RetrieveAndResetLocalErrors(true));
// continue recursive scan
ScanFactoriesRecursive(xmlUtil, configKey, factoryList);
}
continue;
}
case KEYWORD_SECTION: {
string tagName = null;
string typeName = null;
ConfigurationAllowDefinition allowDefinition = ConfigurationAllowDefinition.Everywhere;
ConfigurationAllowExeDefinition allowExeDefinition = ConfigurationAllowExeDefinition.MachineToApplication;
OverrideModeSetting overrideModeDefault = OverrideModeSetting.SectionDefault;
bool allowLocation = true;
bool restartOnExternalChanges = true;
bool requirePermission = true;
bool gotType = false;
// parse section attributes
int lineNumber = xmlUtil.Reader.LineNumber;
while (xmlUtil.Reader.MoveToNextAttribute()) {
switch (xmlUtil.Reader.Name) {
case KEYWORD_SECTION_NAME:
tagName = xmlUtil.Reader.Value;
VerifySectionName(tagName, xmlUtil, ExceptionAction.Local, false, true);
break;
case KEYWORD_SECTION_TYPE:
xmlUtil.VerifyAndGetNonEmptyStringAttribute(ExceptionAction.Local, out typeName);
gotType = true;
break;
case KEYWORD_SECTION_ALLOWLOCATION:
xmlUtil.VerifyAndGetBooleanAttribute(
ExceptionAction.Local, true, out allowLocation);
break;
case KEYWORD_SECTION_ALLOWEXEDEFINITION:
try {
allowExeDefinition = AllowExeDefinitionToEnum(xmlUtil.Reader.Value, xmlUtil);
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Local);
}
break;
case KEYWORD_SECTION_ALLOWDEFINITION:
try {
allowDefinition = AllowDefinitionToEnum(xmlUtil.Reader.Value, xmlUtil);
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Local);
}
break;
case KEYWORD_SECTION_RESTARTONEXTERNALCHANGES:
xmlUtil.VerifyAndGetBooleanAttribute(
ExceptionAction.Local, true, out restartOnExternalChanges);
break;
case KEYWORD_SECTION_REQUIREPERMISSION:
xmlUtil.VerifyAndGetBooleanAttribute(
ExceptionAction.Local, true, out requirePermission);
break;
case KEYWORD_SECTION_OVERRIDEMODEDEFAULT:
try {
overrideModeDefault = OverrideModeSetting.CreateFromXmlReadValue(
OverrideModeSetting.ParseOverrideModeXmlValue(xmlUtil.Reader.Value, xmlUtil));
// Inherit means Allow when comming from the default value
if (overrideModeDefault.OverrideMode == OverrideMode.Inherit) {
overrideModeDefault.ChangeModeInternal(OverrideMode.Allow);
}
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Local);
}
break;
default:
xmlUtil.AddErrorUnrecognizedAttribute(ExceptionAction.Local);
break;
}
}
xmlUtil.Reader.MoveToElement(); // if on an attribute move back to the element
if (!xmlUtil.VerifyRequiredAttribute(
tagName, KEYWORD_SECTION_NAME, ExceptionAction.NonSpecific)) {
//
// Without a name, we cannot continue to create a factoryRecord.
//
xmlUtil.SchemaErrors.RetrieveAndResetLocalErrors(true);
}
else {
// Verify that the Type attribute was present.
// Note that 'typeName' will be null if the attribute was present
// but specified as an empty string.
if (!gotType) {
xmlUtil.AddErrorRequiredAttribute(KEYWORD_SECTION_TYPE, ExceptionAction.Local);
}
// Disallow names starting with "config" unless it is the special configBuilders section.
if (StringUtil.StartsWith(tagName, "config")) {
Type sectionType = Type.GetType(typeName);
if (!StringUtil.Equals(tagName, RESERVED_SECTION_CONFIGURATION_BUILDERS) || sectionType != ConfigurationBuildersSectionType) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_cannot_begin_with_config), xmlUtil);
}
}
string configKey = CombineConfigKey(parentConfigKey, tagName);
FactoryRecord factoryRecord = (FactoryRecord) factoryList[configKey];
if (factoryRecord != null) {
// Error: duplicate section declaration
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_already_defined_at_this_level, tagName), xmlUtil),
ExceptionAction.Local);
} else {
FactoryRecord parentFactoryRecord = _parent.FindFactoryRecord(configKey, true);
if (parentFactoryRecord != null) {
configKey = parentFactoryRecord.ConfigKey;
// make sure that an ancestor has not defined a section with the same name as the group
if (parentFactoryRecord.IsGroup) {
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_already_defined, tagName), xmlUtil),
ExceptionAction.Local);
parentFactoryRecord = null;
}
else if (!parentFactoryRecord.IsEquivalentSectionFactory(Host, typeName, allowLocation, allowDefinition, allowExeDefinition, restartOnExternalChanges, requirePermission)) {
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_already_defined, tagName), xmlUtil),
ExceptionAction.Local);
parentFactoryRecord = null;
}
}
if (parentFactoryRecord != null) {
// Note - Clone will propagate the IsFromTrustedConfigRecord bit,
// which is what we want - if this record is a duplicate of an ancestor,
// the ancestor may be from a trusted config record.
factoryRecord = parentFactoryRecord.CloneSection(xmlUtil.Filename, lineNumber);
}
else {
factoryRecord = new FactoryRecord(
configKey,
parentConfigKey,
tagName,
typeName,
allowLocation,
allowDefinition,
allowExeDefinition,
overrideModeDefault,
restartOnExternalChanges,
requirePermission,
_flags[IsTrusted],
false, // isUndeclared
xmlUtil.Filename,
lineNumber);
}
factoryList[configKey] = factoryRecord;
}
// Add any errors we may have encountered
factoryRecord.AddErrors(xmlUtil.SchemaErrors.RetrieveAndResetLocalErrors(true));
}
}
break;
case KEYWORD_REMOVE: {
string name = null;
int lineNumber = -1;
// parse attributes
while (xmlUtil.Reader.MoveToNextAttribute()) {
if (xmlUtil.Reader.Name != KEYWORD_SECTION_NAME) {
xmlUtil.AddErrorUnrecognizedAttribute(ExceptionAction.NonSpecific);
}
name = xmlUtil.Reader.Value;
lineNumber = xmlUtil.Reader.LineNumber;
}
xmlUtil.Reader.MoveToElement();
if (xmlUtil.VerifyRequiredAttribute(
name, KEYWORD_SECTION_NAME, ExceptionAction.NonSpecific)) {
VerifySectionName(name, xmlUtil, ExceptionAction.NonSpecific, false, true);
}
}
break;
case KEYWORD_CLEAR: {
xmlUtil.VerifyNoUnrecognizedAttributes(ExceptionAction.NonSpecific);
}
break;
default:
xmlUtil.AddErrorUnrecognizedElement(ExceptionAction.NonSpecific);
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
positionedAtNextElement = true;
break;
}
if (!positionedAtNextElement) {
// Need to read to next element, and check if an unrecognized child
// element is found.
xmlUtil.StrictReadToNextElement(ExceptionAction.NonSpecific);
// unrecognized children are not allowed in <configSections>
if (xmlUtil.Reader.Depth > depth + 1) {
xmlUtil.AddErrorUnrecognizedElement(ExceptionAction.NonSpecific);
// Lets try to backup to where we are suppose to be
while (xmlUtil.Reader.Depth > (depth + 1)) {
xmlUtil.ReadToNextElement();
}
}
}
}
}
// ExeDefinitionToEnum
//
// Translate an ExeDefinition string from the Declaration in a file
// to the appropriate enumeration
//
// Parameters:
// allowExeDefinition - string representation of value
// xmlUtil [optional] - can provide better error
//
static internal ConfigurationAllowExeDefinition
AllowExeDefinitionToEnum(string allowExeDefinition, XmlUtil xmlUtil)
{
switch (allowExeDefinition)
{
case KEYWORD_SECTION_ALLOWDEFINITION_MACHINEONLY:
return ConfigurationAllowExeDefinition.MachineOnly;
case KEYWORD_SECTION_ALLOWDEFINITION_MACHINETOAPPLICATION:
return ConfigurationAllowExeDefinition.MachineToApplication;
case KEYWORD_SECTION_ALLOWEXEDEFINITION_MACHTOROAMING:
return ConfigurationAllowExeDefinition.MachineToRoamingUser;
case KEYWORD_SECTION_ALLOWEXEDEFINITION_MACHTOLOCAL:
return ConfigurationAllowExeDefinition.MachineToLocalUser;
default:
throw new ConfigurationErrorsException(
SR.GetString(SR.Config_section_allow_exe_definition_attribute_invalid),
xmlUtil);
}
}
static internal ConfigurationAllowDefinition
AllowDefinitionToEnum(string allowDefinition, XmlUtil xmlUtil) {
switch (xmlUtil.Reader.Value) {
case KEYWORD_SECTION_ALLOWDEFINITION_EVERYWHERE:
return ConfigurationAllowDefinition.Everywhere;
case KEYWORD_SECTION_ALLOWDEFINITION_MACHINEONLY:
return ConfigurationAllowDefinition.MachineOnly;
case KEYWORD_SECTION_ALLOWDEFINITION_MACHINETOAPPLICATION:
return ConfigurationAllowDefinition.MachineToApplication;
case KEYWORD_SECTION_ALLOWDEFINITION_MACHINETOWEBROOT:
return ConfigurationAllowDefinition.MachineToWebRoot;
default:
throw new ConfigurationErrorsException(
SR.GetString(SR.Config_section_allow_definition_attribute_invalid),
xmlUtil);
}
}
static internal string CombineConfigKey(string parentConfigKey, string tagName) {
if (String.IsNullOrEmpty(parentConfigKey)) {
return tagName;
}
if (String.IsNullOrEmpty(tagName)) {
return parentConfigKey;
}
return parentConfigKey + "/" + tagName;
}
static internal void SplitConfigKey(string configKey, out string group, out string name) {
int lastSlash = configKey.LastIndexOf('/');
if (lastSlash == -1) {
group = string.Empty;
name = configKey;
}
else {
group = configKey.Substring(0, lastSlash);
name = configKey.Substring(lastSlash + 1);
}
}
[System.Diagnostics.Conditional("DBG")]
private void DebugValidateIndirectInputs(SectionRecord sectionRecord) {
if (_parent.IsRootConfig) {
return;
}
// Verify that for each indirect input, its target config path is a child path of _parent.
// That's the definition of indirect input.
for(int i = sectionRecord.IndirectLocationInputs.Count-1; i >= 0; i--) {
SectionInput input = sectionRecord.IndirectLocationInputs[i];
// Get the override mode starting from the closest input.
Debug.Assert(UrlPath.IsSubpath(_parent.ConfigPath, input.SectionXmlInfo.TargetConfigPath));
}
}
// Return the lock mode for a section as comming from parent config levels
private OverrideMode ResolveOverrideModeFromParent(string configKey, out OverrideMode childLockMode) {
// When the current record is a location config level we are a direct child of the config level of the actual
// config file inside which the location tag is. For example we have a file d:\inetpub\wwwroot\web.config which
// containts <location path="Sub"> then "this" will be the config level inside the location tag and this.Parent
// is the config level of d:\inetpub\wwwroot\web.config.
// What we will do to come up with the result is:
// 1) Try to find an existing section record somewhere above us.
// If we find an existing section record then it will have the effective value of the lock mode
// that applies to us in it's LockChidlren. We dont need to go further up once we find a section record
// as it has the lock mode of all it's parents accumulated
//
// There is one huge trick though - Location config records are different ( see begining of the func for what a location config record is )
// A location config record is not locked if the config level of the web.config file in which it lives is not locked.
// I.e. when we are looking for the effective value for a location config we have two cases
// a) There is a section record in our immediate parent ( remember our immediate parent is the config file in which we /as a location tag/ are defined )
// In this case our lock mode is not the LockChildren of this section record because this lock mode applies to child config levels in child config files
// The real lock mode for us is the Locked mode of the section record in self.
// b) There is no section record in our immediate parent - in this case the locking is the same as for normal config - LockChildren value of any section
// record we may find above us.
//
// 2) If we can't find an existing section record we have two cases again:
// a) We are at the section declaration level - at this level a section is always unlocked by definition
// If this wasnt so there would be no way to unlock a section that is locked by default
// A Location config is a bit wierd again in a sence that a location config is unlocked if its in the config file where the section is declared
// I.e. if "this" is a location record then a section is unconditionally unlocked if "this.Parent" is the section declaration level
// b) We are not at section declaration level - in this case the result is whatever the default lock mode for the section is ( remember
// that we fall back to the default since we couldnt find a section record with explicit lock mode nowhere above us)
//
// I sure hope that made some sense!
OverrideMode mode = OverrideMode.Inherit;
BaseConfigurationRecord parent = Parent;
BaseConfigurationRecord immediateParent = Parent;
childLockMode = OverrideMode.Inherit;
// Walk the hierarchy until we find an explicit setting for lock state at a config level or we reach to root
while (!parent.IsRootConfig && (mode == OverrideMode.Inherit)) {
SectionRecord sectionRecord = parent.GetSectionRecord(configKey, true);
if (sectionRecord != null) {
// Check for 1a
if (IsLocationConfig && object.ReferenceEquals(immediateParent, parent)) {
// Apply case 1a
mode = sectionRecord.Locked ? OverrideMode.Deny : OverrideMode.Allow;
// In this specific case the lock mode for our children is whatever the children of our parent should inherit
// For example imagine a web.config which has a <location path="." overrideMode="Deny"> and we open "locationSubPath" from this web.config
// The lock for the section is not Deny and will be allow ( see the code line above ). However the chidlren of this location tag
// inherit the lock that applies to the children of the web.config file itself
childLockMode = sectionRecord.LockChildren ? OverrideMode.Deny : OverrideMode.Allow;
}
else {
mode = sectionRecord.LockChildren ? OverrideMode.Deny : OverrideMode.Allow;
// When the lock mode is comming from a parent level the
// lock mode that applies to children of "this" is the same as what applies to "this"
childLockMode = mode;
}
}
parent = parent._parent;
}
// Case 2
if (mode == OverrideMode.Inherit) {
Debug.Assert(FindFactoryRecord(configKey, true) != null);
bool atDeclarationLevel = false;
OverrideMode defaultMode = FindFactoryRecord(configKey, true).OverrideModeDefault.OverrideMode;
if (IsLocationConfig) {
atDeclarationLevel = this.Parent.GetFactoryRecord(configKey, true) != null;
}
else {
atDeclarationLevel = this.GetFactoryRecord(configKey, true) != null;
}
if (!atDeclarationLevel) {
// 2b
/////////
// Lock mode for children and self is the same since the default value is comming
// from a parent level and hence - applies to both
childLockMode = mode = defaultMode;
Debug.Assert(mode != OverrideMode.Inherit); // Remember that the default is never Inherit
}
else {
// 2a
////////
// Self is always allow at section declaration level
// Child lock mode is the default value ( remember we are here because no explici mode was set anywhere above us )
mode = OverrideMode.Allow;
childLockMode = defaultMode;
}
}
// This function must return Allow or Deny
Debug.Assert(mode != OverrideMode.Inherit);
return mode;
}
protected OverrideMode GetSectionLockedMode(string configKey) {
OverrideMode dummy = OverrideMode.Inherit;
return GetSectionLockedMode(configKey, out dummy);
}
// Return the current lock mode for a section
protected OverrideMode GetSectionLockedMode(string configKey, out OverrideMode childLockMode) {
OverrideMode result = OverrideMode.Inherit;
SectionRecord sectionRecord = GetSectionRecord(configKey, true);
// If there is a section record it has the effective locking settings resolved
// There is no need to do ResolveOverrideModeFromParent because it was done in:
// 1) In EnsureSectionRecord when the section record was creteted
// 2) Right after the SectionRecord was created without initialization of the lock settings
// in this:ScanSectionsRecursive().
// As long as nobody uses EnsureSectionRecordUnsafe this method will be returning the correct
// lock value only by looking at the section record
if (sectionRecord != null) {
result = sectionRecord.Locked ? OverrideMode.Deny : OverrideMode.Allow;
childLockMode = sectionRecord.LockChildren ? OverrideMode.Deny : OverrideMode.Allow;
}
else {
result = ResolveOverrideModeFromParent(configKey, out childLockMode);
}
return result;
}
private void ScanSections(XmlUtil xmlUtil) {
ScanSectionsRecursive(xmlUtil, string.Empty, false, null, OverrideModeSetting.LocationDefault, false);
}
private void ScanSectionsRecursive(
XmlUtil xmlUtil,
string parentConfigKey,
bool inLocation,
string locationSubPath,
OverrideModeSetting overrideMode,
bool skipInChildApps) {
// discard any accumulated local errors
xmlUtil.SchemaErrors.ResetLocalErrors();
int depth;
// only move to child nodes when not on first level (we've already passed the first <configsections>)
if (parentConfigKey.Length == 0 && !inLocation) {
depth = 0;
}
else {
depth = xmlUtil.Reader.Depth;
xmlUtil.StrictReadToNextElement(ExceptionAction.NonSpecific);
}
while (xmlUtil.Reader.Depth == depth + 1) {
string tagName = xmlUtil.Reader.Name;
//
// Check for reserved elements before looking up the factory,
// which may have the same name if it is in error.
//
if (tagName == KEYWORD_CONFIGSECTIONS) {
// Error: duplicate <configSections> tag, or <configSections> not the first tag under <configuration>
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_client_config_too_many_configsections_elements, tagName), xmlUtil),
ExceptionAction.NonSpecific);
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
continue;
}
if (tagName == KEYWORD_LOCATION) {
if (parentConfigKey.Length > 0 || inLocation) {
// Error: <location> section not at top level
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_location_location_not_allowed), xmlUtil),
ExceptionAction.Global);
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
}
else {
// Recurse into the location section
ScanLocationSection(xmlUtil);
}
continue;
}
string configKey = CombineConfigKey(parentConfigKey, tagName);
FactoryRecord factoryRecord = FindFactoryRecord(configKey, true);
if (factoryRecord == null) {
//
// Unregistered configuration section
//
// At runtime, it is a local error to have an unrecognized section.
// By treating it as local we avoid throwing an error if the
// section is encountered within a location section, just as we treat
// other section errors in a location tag.
//
// At designtime, we do not consider it an error, so that programs
// that worked on version N config files can continue to work with
// version N+1 config files that may introduce new sections.
//
if (!ClassFlags[ClassIgnoreLocalErrors]) {
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_unrecognized_configuration_section, configKey), xmlUtil),
ExceptionAction.Local);
}
VerifySectionName(tagName, xmlUtil, ExceptionAction.Local, false);
factoryRecord = new FactoryRecord(
configKey,
parentConfigKey,
tagName,
typeof(DefaultSection).AssemblyQualifiedName,
true, // allowLocation
ConfigurationAllowDefinition.Everywhere,
ConfigurationAllowExeDefinition.MachineToRoamingUser,
OverrideModeSetting.SectionDefault,
true, // restartOnExternalChanges
true, // requirePermission
_flags[IsTrusted],
true, // isUndeclared
null,
-1);
// Add any errors we may have encountered to the factory record,
// so that child config that also refer to this unrecognized section
// get the error.
factoryRecord.AddErrors(xmlUtil.SchemaErrors.RetrieveAndResetLocalErrors(true));
// Add the factory to the list of factories
EnsureFactories()[configKey] = factoryRecord;
}
if (factoryRecord.IsGroup) {
//
// Section Group
//
if (factoryRecord.HasErrors) {
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
}
else {
if (xmlUtil.Reader.AttributeCount > 0) {
// We allow unrecognized attributes for backward compatibility (VSWhidbey 516534)
// However, we will still throw if the unrecognized attribute is reserved.
while (xmlUtil.Reader.MoveToNextAttribute()) {
if (IsReservedAttributeName(xmlUtil.Reader.Name)) {
xmlUtil.AddErrorReservedAttribute(ExceptionAction.NonSpecific);
}
}
xmlUtil.Reader.MoveToElement(); // if on an attribute move back to the element
}
// Recurse into group definition
ScanSectionsRecursive(xmlUtil, configKey, inLocation, locationSubPath, overrideMode, skipInChildApps);
}
}
else {
//
// Section
//
configKey = factoryRecord.ConfigKey;
string fileName = xmlUtil.Filename;
int lineNumber = xmlUtil.LineNumber;
string rawXml = null;
string configSource = null;
string configSourceStreamName = null;
object configSourceStreamVersion = null;
string configBuilderName = null;
string protectionProviderName = null;
OverrideMode sectionLockMode = OverrideMode.Inherit;
OverrideMode sectionChildLockMode = OverrideMode.Inherit;
bool positionedAtNextElement = false;
bool isFileInput = (locationSubPath == null);
if (!factoryRecord.HasErrors) {
// We have a valid factoryRecord for a section
if (inLocation && factoryRecord.AllowLocation == false) {
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_section_cannot_be_used_in_location), xmlUtil),
ExceptionAction.Local);
}
// Verify correctness for file inputs.
if (isFileInput) {
// Verify that the section is unique
SectionRecord sectionRecord = GetSectionRecord(configKey, true);
if (sectionRecord != null && sectionRecord.HasFileInput) {
if (!(factoryRecord.IsIgnorable())) {
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_sections_must_be_unique), xmlUtil),
ExceptionAction.Local);
}
}
// Verify that the definition is allowed.
try {
VerifyDefinitionAllowed(factoryRecord, _configPath, xmlUtil);
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Local);
}
}
// Verify that section is unlocked, both for file and location inputs.
sectionLockMode = GetSectionLockedMode(configKey, out sectionChildLockMode);
if (sectionLockMode == OverrideMode.Deny) {
xmlUtil.SchemaErrors.AddError( new ConfigurationErrorsException(SR.GetString(SR.Config_section_locked), xmlUtil),
ExceptionAction.Local);
}
// check for configSource or protectionProvider
if (xmlUtil.Reader.AttributeCount >= 1) {
// First do all the attributes reading without advancing the reader.
string configSourceAttribute = xmlUtil.Reader.GetAttribute(KEYWORD_CONFIGSOURCE);
if (configSourceAttribute != null) {
try {
configSource = NormalizeConfigSource(configSourceAttribute, xmlUtil);
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Local);
}
if (xmlUtil.Reader.AttributeCount != 1) {
// Error: elements with configSource should not have other attributes
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_source_syntax_error), xmlUtil),
ExceptionAction.Local);
}
}
string protectionProviderAttribute = xmlUtil.Reader.GetAttribute(KEYWORD_PROTECTION_PROVIDER);
if (protectionProviderAttribute != null) {
try {
protectionProviderName = ValidateProtectionProviderAttribute(protectionProviderAttribute, xmlUtil);
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Local);
}
if (xmlUtil.Reader.AttributeCount != 1) {
// Error: elements with protectionProvider should not have other attributes
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Protection_provider_syntax_error), xmlUtil),
ExceptionAction.Local);
}
}
string configBuilderAttribute = xmlUtil.Reader.GetAttribute(KEYWORD_CONFIG_BUILDER);
if (configBuilderAttribute != null) {
try {
configBuilderName = ValidateConfigBuilderAttribute(configBuilderAttribute, xmlUtil);
}
catch (ConfigurationException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Local);
}
}
// The 2nd part of the configSource check requires advancing the reader.
// Please note that this part should be done only AFTER all other attributes
// checking are done.
if (configSourceAttribute != null) {
if (!xmlUtil.Reader.IsEmptyElement) {
while (xmlUtil.Reader.Read()) {
XmlNodeType t = xmlUtil.Reader.NodeType;
if (t == XmlNodeType.EndElement)
break;
if (t != XmlNodeType.Comment) {
// Error: elements with configSource should not subelements other than comments
xmlUtil.SchemaErrors.AddError(
new ConfigurationErrorsException(SR.GetString(SR.Config_source_syntax_error), xmlUtil),
ExceptionAction.Local);
if (t == XmlNodeType.Element) {
xmlUtil.StrictSkipToOurParentsEndElement(ExceptionAction.NonSpecific);
}
else {
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
}
positionedAtNextElement = true;
break;
}
}
}
}
}
if (configSource != null) {
try {
try {
configSourceStreamName = Host.GetStreamNameForConfigSource(ConfigStreamInfo.StreamName, configSource);
}
catch (Exception e) {
throw ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_source_invalid), e, xmlUtil);
}
ValidateUniqueConfigSource(configKey, configSourceStreamName, configSource, xmlUtil);
configSourceStreamVersion = MonitorStream(configKey, configSource, configSourceStreamName);
}
catch (ConfigurationException ex) {
xmlUtil.SchemaErrors.AddError(ex, ExceptionAction.Local);
}
}
//
// prefetch the raw xml
//
if (!xmlUtil.SchemaErrors.HasLocalErrors) {
if (configSource == null && ShouldPrefetchRawXml(factoryRecord)) {
Debug.Assert(!positionedAtNextElement, "!positionedAtNextElement");
rawXml = xmlUtil.CopySection();
if (xmlUtil.Reader.NodeType != XmlNodeType.Element) {
xmlUtil.VerifyIgnorableNodeType(ExceptionAction.NonSpecific);
xmlUtil.StrictReadToNextElement(ExceptionAction.NonSpecific);
}
positionedAtNextElement = true;
}
}
}
// Get the list of errors before advancing the reader
List<ConfigurationException> localErrors = xmlUtil.SchemaErrors.RetrieveAndResetLocalErrors(isFileInput);
// advance the reader to the next element
if (!positionedAtNextElement) {
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
}
// Add the input either to:
// 1. The file input at the current config level, or
// 2. LocationSections, where it will be used in sub paths
bool addInput = true;
if (isFileInput) {
// If isFileInput==true, Input added will be used against this config level.
// Need to check if we need to skip it due to inheritInChildApplications.
if (ShouldSkipDueToInheritInChildApplications(skipInChildApps)) {
addInput = false;
}
}
else {
if (!_flags[SupportsLocation]) {
// Skip if we have a location input but we don't support location tag.
addInput = false;
}
}
if (addInput) {
string targetConfigPath = (locationSubPath == null) ? _configPath : null;
SectionXmlInfo sectionXmlInfo = new SectionXmlInfo(
configKey, _configPath, targetConfigPath, locationSubPath,
fileName, lineNumber, ConfigStreamInfo.StreamVersion, rawXml,
configSource, configSourceStreamName, configSourceStreamVersion,
configBuilderName, protectionProviderName, overrideMode, skipInChildApps);
if (locationSubPath == null) {
//
// Add this file input to the section record
//
// We've already checked for locked above, so use skip the second check
// and set the locked bit.
SectionRecord sectionRecord = EnsureSectionRecordUnsafe(configKey, true);
// Since we called EnsureSectionRecordUnsafe the section record does not have its lock mode resolved
// but we have it in sectionLockMode and childLockMode. Apply it now
sectionRecord.ChangeLockSettings(sectionLockMode, sectionChildLockMode);
// Note that we first apply the lock mode comming from parent levels ( the line above ) and then
// add the file input since the file input takes precedence over whats comming from parent
SectionInput fileInput = new SectionInput(sectionXmlInfo, localErrors);
sectionRecord.AddFileInput(fileInput);
}
else {
//
// Add this location input to this list of location sections
//
LocationSectionRecord locationSectionRecord = new LocationSectionRecord(sectionXmlInfo, localErrors);
EnsureLocationSections().Add(locationSectionRecord);
}
}
}
}
}
private void ScanLocationSection(XmlUtil xmlUtil) {
string locationSubPath = null;
bool inheritInChildApp = true;
int errorCountBeforeScan = xmlUtil.SchemaErrors.GlobalErrorCount;
OverrideModeSetting overrideMode = OverrideModeSetting.LocationDefault;
bool overrideModeInit = false;
// Get the location section attributes
while (xmlUtil.Reader.MoveToNextAttribute()) {
switch (xmlUtil.Reader.Name) {
case KEYWORD_LOCATION_PATH:
locationSubPath = xmlUtil.Reader.Value;
break;
case KEYWORD_LOCATION_ALLOWOVERRIDE:
// Check that allowOverride and OverrideMode werent specified at the same time
if (overrideModeInit == true){
xmlUtil.SchemaErrors.AddError(new ConfigurationErrorsException(SR.GetString(SR.Invalid_override_mode_declaration), xmlUtil), ExceptionAction.Global);
}
else {
bool value = true;
// Read the value
xmlUtil.VerifyAndGetBooleanAttribute(
ExceptionAction.Global, true, out value);
overrideMode = OverrideModeSetting.CreateFromXmlReadValue(value);
overrideModeInit = true;
}
break;
case KEYWORD_LOCATION_OVERRIDEMODE:
if (overrideModeInit == true){
xmlUtil.SchemaErrors.AddError(new ConfigurationErrorsException(SR.GetString(SR.Invalid_override_mode_declaration), xmlUtil), ExceptionAction.Global);
}
else {
overrideMode = OverrideModeSetting.CreateFromXmlReadValue(
OverrideModeSetting.ParseOverrideModeXmlValue(xmlUtil.Reader.Value, xmlUtil));
overrideModeInit = true;
}
break;
case KEYWORD_LOCATION_INHERITINCHILDAPPLICATIONS:
xmlUtil.VerifyAndGetBooleanAttribute(
ExceptionAction.Global, true, out inheritInChildApp);
break;
default:
xmlUtil.AddErrorUnrecognizedAttribute(ExceptionAction.Global);
break;
}
}
xmlUtil.Reader.MoveToElement(); // if on an attribute move back to the element
try {
locationSubPath = NormalizeLocationSubPath(locationSubPath, xmlUtil);
// VSWhidbey 535595
// See attached email in the bug. Basically, we decided to throw if we see one of these
// in machine.config or root web.config:
// <location path="." inheritInChildApplications="false" >
// <location inheritInChildApplications="false" >
//
// To detect whetherewe're machine.config or root web.config, the current fix is to use
// Host.IsDefinitionAllowed. Instead of this we should invent a new method in
// IInternalConfigHost to return whether a configPath can be part of an app or not.
// But since it's Whidbey RC "Ask Mode" I chose not to do it due to bigger code churn.
//
//
if (locationSubPath == null &&
!inheritInChildApp &&
Host.IsDefinitionAllowed(_configPath, ConfigurationAllowDefinition.MachineToWebRoot, ConfigurationAllowExeDefinition.MachineOnly)) {
throw new ConfigurationErrorsException(SR.GetString(SR.Location_invalid_inheritInChildApplications_in_machine_or_root_web_config), xmlUtil);
}
}
catch (ConfigurationErrorsException ce) {
xmlUtil.SchemaErrors.AddError(ce, ExceptionAction.Global);
}
// Skip over this location section if there are errors
if (xmlUtil.SchemaErrors.GlobalErrorCount > errorCountBeforeScan) {
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
return;
}
// Scan elements of the location section if the path is the current path.
// We do not add <location path="." /> to the _locationSections list.
if (locationSubPath == null) {
ScanSectionsRecursive(xmlUtil, string.Empty, true, null, overrideMode, !inheritInChildApp);
return;
}
// Skip over location sections for client config
if (!_flags[SupportsLocation]) {
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
return;
}
// WOS 1955773: (Perf) 4,000 location sections in web.config file degrades working set
// Skip over location sections that don't apply to this (application) host
// WOS 1983387: do this for the runtime record only. It's a valid scenario for
// mgt config record
IInternalConfigHost host = Host;
if ((this is RuntimeConfigurationRecord) && host != null && locationSubPath.Length != 0 && locationSubPath[0] != '.') {
// The application's config path is global to the AppDomain
if (s_appConfigPath == null) {
object ctx = ConfigContext;
if (ctx != null) {
string appConfigPath = ctx.ToString();
Interlocked.CompareExchange(ref s_appConfigPath, appConfigPath, null);
}
}
// If targetConfigPath is not upstream or downstream of the application's config path,
// skip this location section.
//
// Example #1: <location path="Site1"> has a targetConfigPath of "machine/webroot/1". This applies
// to Site1, whose application config path is "machine/webroot/1", but it does not apply
// to Site2, whose application config path is "machine/webroot/2"
//
// Example #2: <location path="subdir"> has a targetConfigPath of "machine/webroot/1/root/subdir".
// This applies to an application with an application config path of "machine/webroot/1/root/subdir/app".
//
string targetConfigPath = host.GetConfigPathFromLocationSubPath(_configPath, locationSubPath);
if (!StringUtil.StartsWithIgnoreCase(s_appConfigPath, targetConfigPath)
&& !StringUtil.StartsWithIgnoreCase(targetConfigPath, s_appConfigPath)) {
xmlUtil.StrictSkipToNextElement(ExceptionAction.NonSpecific);
return;
}
}
AddLocation(locationSubPath);
ScanSectionsRecursive(xmlUtil, string.Empty, true, locationSubPath, overrideMode, !inheritInChildApp);
}
// AddLocation
//
// If you wish to keep track of the Location Fields, then use this
//
protected virtual void AddLocation(string LocationSubPath) {}
//
// Resolve information about a location section at the time that the location section
// is being used by child configuration records. This allows us to:
// * Delay determining the configuration path for the location record until the sites section is available.
// * Delay reporting bad location paths until the location record has to be used.
//
private void ResolveLocationSections() {
if (!_flags[IsLocationListResolved]) {
// Resolve outside of any lock
if (!_parent.IsRootConfig) {
_parent.ResolveLocationSections();
}
lock (this) {
if (!_flags[IsLocationListResolved]) {
if (_locationSections != null) {
//
// Create dictionary that maps configPaths to (dictionary that maps sectionNames to locationSectionRecords)
//
HybridDictionary locationConfigPaths = new HybridDictionary(true);
foreach (LocationSectionRecord locationSectionRecord in _locationSections) {
//
// Resolve the target config path
//
string targetConfigPath = Host.GetConfigPathFromLocationSubPath(_configPath, locationSectionRecord.SectionXmlInfo.SubPath);
locationSectionRecord.SectionXmlInfo.TargetConfigPath = targetConfigPath;
//
// Check uniqueness
//
HybridDictionary locationSectionRecordDictionary = (HybridDictionary) locationConfigPaths[targetConfigPath];
if (locationSectionRecordDictionary == null) {
locationSectionRecordDictionary = new HybridDictionary(false);
locationConfigPaths.Add(targetConfigPath, locationSectionRecordDictionary);
}
LocationSectionRecord duplicateRecord = (LocationSectionRecord) locationSectionRecordDictionary[locationSectionRecord.ConfigKey];
FactoryRecord factoryRecord = null;
if (duplicateRecord == null) {
locationSectionRecordDictionary.Add(locationSectionRecord.ConfigKey, locationSectionRecord);
}
else {
factoryRecord = FindFactoryRecord(locationSectionRecord.ConfigKey, true);
if (factoryRecord == null || !(factoryRecord.IsIgnorable())) {
if (!duplicateRecord.HasErrors) {
duplicateRecord.AddError(
new ConfigurationErrorsException(
SR.GetString(SR.Config_sections_must_be_unique),
duplicateRecord.SectionXmlInfo));
}
locationSectionRecord.AddError(
new ConfigurationErrorsException(
SR.GetString(SR.Config_sections_must_be_unique),
locationSectionRecord.SectionXmlInfo));
}
}
//
// Check if the definition is allowed
//
if (factoryRecord == null)
factoryRecord = FindFactoryRecord(locationSectionRecord.ConfigKey, true);
if (!factoryRecord.HasErrors) {
try {
VerifyDefinitionAllowed(factoryRecord, targetConfigPath, locationSectionRecord.SectionXmlInfo);
}
catch (ConfigurationException e) {
locationSectionRecord.AddError(e);
}
}
}
//
// Check location section for being locked.
//
BaseConfigurationRecord parent = _parent;
while (!parent.IsRootConfig) {
foreach (LocationSectionRecord locationSectionRecord in this._locationSections) {
bool locked = false;
//
// It is an error if a parent section with the same configKey is locked.
//
SectionRecord sectionRecord = parent.GetSectionRecord(locationSectionRecord.ConfigKey, true);
if ( sectionRecord != null &&
(sectionRecord.LockChildren || sectionRecord.Locked)) {
locked = true;
}
else {
//
// It is an error if a parent configuration file locks a section for the
// locationConfigPath or any sub-path of the locationConfigPath.
//
if (parent._locationSections != null) {
string targetConfigPath = locationSectionRecord.SectionXmlInfo.TargetConfigPath;
foreach (LocationSectionRecord parentLocationSectionRecord in parent._locationSections) {
string parentTargetConfigPath = parentLocationSectionRecord.SectionXmlInfo.TargetConfigPath;
if ( parentLocationSectionRecord.SectionXmlInfo.OverrideModeSetting.IsLocked &&
locationSectionRecord.ConfigKey == parentLocationSectionRecord.ConfigKey &&
UrlPath.IsEqualOrSubpath(targetConfigPath, parentTargetConfigPath)) {
locked = true;
break;
}
}
}
}
if (locked) {
locationSectionRecord.AddError(new ConfigurationErrorsException(
SR.GetString(SR.Config_section_locked),
locationSectionRecord.SectionXmlInfo));
}
}
parent = parent._parent;
}
}
}
_flags[IsLocationListResolved] = true;
}
}
}
// VerifyDefinitionAllowed
//
// Verify that the Definition is allowed at this
// place.
//
// For example, if this config record is an application then
// make sure the section say's it can be defined in an
// application
//
private void VerifyDefinitionAllowed(FactoryRecord factoryRecord, string configPath, IConfigErrorInfo errorInfo) {
Host.VerifyDefinitionAllowed(configPath, factoryRecord.AllowDefinition, factoryRecord.AllowExeDefinition, errorInfo);
}
internal bool IsDefinitionAllowed(ConfigurationAllowDefinition allowDefinition, ConfigurationAllowExeDefinition allowExeDefinition) {
return Host.IsDefinitionAllowed(_configPath, allowDefinition, allowExeDefinition);
}
static protected void VerifySectionName(string name, XmlUtil xmlUtil, ExceptionAction action, bool allowImplicit, bool allowConfigNames = false) {
try {
VerifySectionName(name, (IConfigErrorInfo) xmlUtil, allowImplicit, allowConfigNames);
}
catch (ConfigurationErrorsException ce) {
xmlUtil.SchemaErrors.AddError(ce, action);
}
}
// Check if the section name contains reserved words from the config system,
// and is a valid name for an XML Element.
static protected void VerifySectionName(string name, IConfigErrorInfo errorInfo, bool allowImplicit, bool allowConfigNames = false) {
if (String.IsNullOrEmpty(name)) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_invalid), errorInfo);
}
// must be a valid name in xml, so that it can be used as an element
// n.b. - it also excludes forward slash '/'
try {
XmlConvert.VerifyName(name);
}
// Do not let the exception propagate as an XML exception,
// for we want errors in the section name to be treated as local errors,
// not global ones.
catch (Exception e) {
throw ExceptionUtil.WrapAsConfigException(SR.GetString(SR.Config_tag_name_invalid), e, errorInfo);
}
if (IsImplicitSection(name)) {
if (allowImplicit) {
// avoid test below for strings starting with "config"
return;
}
else {
throw new ConfigurationErrorsException(SR.GetString(SR.Cannot_declare_or_remove_implicit_section, name), errorInfo);
}
}
if (!allowConfigNames && StringUtil.StartsWith(name, "config")) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_cannot_begin_with_config), errorInfo);
}
if (name == KEYWORD_LOCATION) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_tag_name_cannot_be_location), errorInfo);
}
}
/*
From http://www.w3.org/Addressing/
reserved = ';' | '/' | '?' | ':' | '@' | '&' | '=' | '+' | '$' | ','
From Platform SDK
reserved = '\' | '/' | '|' | ':' | '"' | '<' | '>'
*/
// NOTE: If you change these strings, you must change the associated error message
const string invalidFirstSubPathCharacters = @"\./";
const string invalidLastSubPathCharacters = @"\./";
const string invalidSubPathCharactersString = @"\?:*""<>|";
static char[] s_invalidSubPathCharactersArray = invalidSubPathCharactersString.ToCharArray();
// Return null if the subPath represents the current directory, for example:
// path=""
// path=" "
// path="."
// path="./"
internal static string NormalizeLocationSubPath(string subPath, IConfigErrorInfo errorInfo) {
// if subPath is null or empty, it is the current dir
if (String.IsNullOrEmpty(subPath))
return null;
// if subPath=".", it is the current dir
if (subPath == ".")
return null;
// do not allow whitespace in front of subPath, as the OS
// handles beginning and trailing whitespace inconsistently
string trimmedSubPath = subPath.TrimStart();
if (trimmedSubPath.Length != subPath.Length) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_location_path_invalid_first_character), errorInfo);
}
// do not allow problematic starting characters
if (invalidFirstSubPathCharacters.IndexOf(subPath[0]) != -1) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_location_path_invalid_first_character), errorInfo);
}
// do not allow whitespace at end of subPath, as the OS
// handles beginning and trailing whitespace inconsistently
trimmedSubPath = subPath.TrimEnd();
if (trimmedSubPath.Length != subPath.Length) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_location_path_invalid_last_character), errorInfo);
}
// the file system ignores trailing '.', '\', or '/', so do not allow it in a location subpath specification
if (invalidLastSubPathCharacters.IndexOf(subPath[subPath.Length-1]) != -1) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_location_path_invalid_last_character), errorInfo);
}
// combination of URI reserved characters and OS invalid filename characters, minus / (allowed reserved character)
if (subPath.IndexOfAny(s_invalidSubPathCharactersArray) != -1) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_location_path_invalid_character), errorInfo);
}
return subPath;
}
//
// Return the SectionRecord for a section.
// If the record does not exist, return null.
// Throw cached errors if the section is in error and permitErrors == false.
//
protected SectionRecord GetSectionRecord(string configKey, bool permitErrors) {
SectionRecord sectionRecord;
if (_sectionRecords != null) {
sectionRecord = (SectionRecord) _sectionRecords[configKey];
}
else {
sectionRecord = null;
}
if (sectionRecord != null && !permitErrors) {
sectionRecord.ThrowOnErrors();
}
return sectionRecord;
}
// Return an existing SectionRecord, or create one if one does not exist.
// Propagate the Locked bit from parent
protected SectionRecord EnsureSectionRecord(string configKey, bool permitErrors) {
return EnsureSectionRecordImpl(configKey, permitErrors, true);
}
// Return an existing SectionRecord, or create one if one does not exist.
// Do not propagate the Locked bit from parent, because caller will check
// himself later.
protected SectionRecord EnsureSectionRecordUnsafe(string configKey, bool permitErrors) {
return EnsureSectionRecordImpl(configKey, permitErrors, false);
}
// Return an existing SectionRecord, or create one if one does not exist.
// If desired, set the lock settings based on parent configs.
private SectionRecord EnsureSectionRecordImpl(string configKey, bool permitErrors, bool setLockSettings) {
SectionRecord sectionRecord = GetSectionRecord(configKey, permitErrors);
if (sectionRecord == null) {
lock (this) {
if (_sectionRecords == null) {
_sectionRecords = new Hashtable();
}
else {
sectionRecord = GetSectionRecord(configKey, permitErrors);
}
if (sectionRecord == null) {
sectionRecord = new SectionRecord(configKey);
_sectionRecords.Add(configKey, sectionRecord);
}
}
if (setLockSettings) {
// Get the lock mode from parent configs
OverrideMode parentMode = OverrideMode.Inherit;
OverrideMode childLockMode = OverrideMode.Inherit;
parentMode = ResolveOverrideModeFromParent(configKey, out childLockMode);
sectionRecord.ChangeLockSettings(parentMode, childLockMode);
}
}
return sectionRecord;
}
private bool HasFactoryRecords {
get {
return _factoryRecords != null;
}
}
internal FactoryRecord GetFactoryRecord(string configKey, bool permitErrors) {
FactoryRecord factoryRecord;
if (_factoryRecords == null) {
return null;
}
factoryRecord = (FactoryRecord) _factoryRecords[configKey];
if (factoryRecord != null && !permitErrors) {
factoryRecord.ThrowOnErrors();
}
return factoryRecord;
}
// Only create a _factories hashtable when necessary.
// Most config records won't have factories, so we can save 120 bytes
// per record by creating the table on demand.
protected Hashtable EnsureFactories() {
if (_factoryRecords == null) {
_factoryRecords = new Hashtable();
}
return _factoryRecords;
}
private ArrayList EnsureLocationSections() {
if (_locationSections == null) {
_locationSections = new ArrayList();
}
return _locationSections;
}
// Return true if there is no unique configuration information in this record.
internal bool IsEmpty {
get {
return
_parent != null
&& !_initErrors.HasErrors(false)
&& (_sectionRecords == null || _sectionRecords.Count == 0)
&& (_factoryRecords == null || _factoryRecords.Count == 0)
&& (_locationSections == null || _locationSections.Count == 0);
}
}
static internal string NormalizeConfigSource(string configSource, IConfigErrorInfo errorInfo) {
if (String.IsNullOrEmpty(configSource)) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_source_invalid_format), errorInfo);
}
string trimmedConfigSource = configSource.Trim();
if (trimmedConfigSource.Length != configSource.Length) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_source_invalid_format), errorInfo);
}
if (configSource.IndexOf('/') != -1) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_source_invalid_chars), errorInfo);
}
if (String.IsNullOrEmpty(configSource) || System.IO.Path.IsPathRooted(configSource)) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_source_invalid_format), errorInfo);
}
return configSource;
}
protected object MonitorStream(string configKey, string configSource, string streamname) {
lock (this) {
if (_flags[Closed]) {
return null;
}
StreamInfo streamInfo = (StreamInfo) ConfigStreamInfo.StreamInfos[streamname];
if (streamInfo != null) {
if (streamInfo.SectionName != configKey) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_source_cannot_be_shared, streamname));
}
if (streamInfo.IsMonitored) {
return streamInfo.Version;
}
}
else {
streamInfo = new StreamInfo(configKey, configSource, streamname);
ConfigStreamInfo.StreamInfos.Add(streamname, streamInfo);
}
}
//
// Call the host outside the lock to avoid deadlock.
//
object version = Host.GetStreamVersion(streamname);
StreamChangeCallback callbackDelegate = null;
lock (this) {
if (_flags[Closed]) {
return null;
}
StreamInfo streamInfo = (StreamInfo) ConfigStreamInfo.StreamInfos[streamname];
if (streamInfo.IsMonitored) {
return streamInfo.Version;
}
streamInfo.IsMonitored = true;
streamInfo.Version = version;
if (_flags[SupportsChangeNotifications]) {
if (ConfigStreamInfo.CallbackDelegate == null) {
ConfigStreamInfo.CallbackDelegate = new StreamChangeCallback(this.OnStreamChanged);
}
callbackDelegate = ConfigStreamInfo.CallbackDelegate;
}
}
if (_flags[SupportsChangeNotifications]) {
Host.StartMonitoringStreamForChanges(streamname, callbackDelegate);
}
return version;
}
private void OnStreamChanged(string streamname) {
bool notifyChanged;
StreamInfo streamInfo;
string sectionName;
lock (this) {
if (_flags[Closed])
return;
streamInfo = (StreamInfo) ConfigStreamInfo.StreamInfos[streamname];
if (streamInfo == null || !streamInfo.IsMonitored)
return;
sectionName = streamInfo.SectionName;
}
if (sectionName == null) {
notifyChanged = true;
}
else {
FactoryRecord factoryRecord = FindFactoryRecord(sectionName, false);
notifyChanged = factoryRecord.RestartOnExternalChanges;
}
if (notifyChanged) {
_configRoot.FireConfigChanged(_configPath);
}
else {
_configRoot.ClearResult(this, sectionName, false);
}
}
// ValidateUniqueConfigSource
//
// Validate that the configSource is unique for this particular
// configKey. This looks up at the parents and makes sure it is
// unique. It if is in a child, then it's check will find this
// one. If it is in a peer, then we don't care as much, since it
// will not affect Merge and UnMerge
//
// See VSWhidbey 460219 for details.
//
private void ValidateUniqueConfigSource(
string configKey, string configSourceStreamName, string configSourceArg, IConfigErrorInfo errorInfo) {
//
// Detect if another section in this file is using the same configSource
// with has a different section name.
//
lock (this) {
if (ConfigStreamInfo.HasStreamInfos) {
StreamInfo streamInfo = (StreamInfo) ConfigStreamInfo.StreamInfos[configSourceStreamName];
if (streamInfo != null && streamInfo.SectionName != configKey) {
throw new ConfigurationErrorsException(
SR.GetString(SR.Config_source_cannot_be_shared, configSourceArg),
errorInfo);
}
}
}
ValidateUniqueChildConfigSource(configKey, configSourceStreamName, configSourceArg, errorInfo);
}
protected void ValidateUniqueChildConfigSource(
string configKey, string configSourceStreamName, string configSourceArg, IConfigErrorInfo errorInfo) {
//
// Detect if a parent config file is using the same config source stream.
//
BaseConfigurationRecord current;
if (IsLocationConfig) {
current = _parent._parent;
}
else {
current = _parent;
}
while (!current.IsRootConfig) {
lock (current) {
if (current.ConfigStreamInfo.HasStreamInfos) {
StreamInfo streamInfo = (StreamInfo) current.ConfigStreamInfo.StreamInfos[configSourceStreamName];
if (streamInfo != null) {
throw new ConfigurationErrorsException(
SR.GetString(SR.Config_source_parent_conflict, configSourceArg),
errorInfo);
}
}
}
current = current.Parent;
}
}
// Recursively clear the result.
// If forceEvaluation == true, force a rescan of the config file to find
// the section.
// Requires the hierarchy lock to be acquired (hl)
internal void hlClearResultRecursive(string configKey, bool forceEvaluatation) {
SectionRecord sectionRecord;
// Refresh it's factory Record
RefreshFactoryRecord(configKey);
// Clear any stored result in the section
sectionRecord = GetSectionRecord(configKey, false);
if (sectionRecord != null) {
sectionRecord.ClearResult();
// VSWhidbey 535724: Need to clear all RawXml so that when GetSectionXmlReader
// is called later it will reload the file.
sectionRecord.ClearRawXml();
}
//
// If we need to reevaluate, add a dummy file input so
// that we open the file on the next evaluation
//
if (forceEvaluatation && !IsInitDelayed && !String.IsNullOrEmpty(ConfigStreamInfo.StreamName)) {
if (_flags[SupportsPath]) {
throw ExceptionUtil.UnexpectedError("BaseConfigurationRecord::hlClearResultRecursive");
}
FactoryRecord factoryRecord = FindFactoryRecord(configKey, false);
if (factoryRecord != null && !factoryRecord.IsGroup) {
configKey = factoryRecord.ConfigKey;
sectionRecord = EnsureSectionRecord(configKey, false);
if (!sectionRecord.HasFileInput) {
SectionXmlInfo sectionXmlInfo = new SectionXmlInfo(
configKey, _configPath, _configPath, null,
ConfigStreamInfo.StreamName, 0, null, null,
null, null, null, null,
null, OverrideModeSetting.LocationDefault, false);
SectionInput fileInput = new SectionInput(sectionXmlInfo, null);
sectionRecord.AddFileInput(fileInput);
}
}
}
// Recurse
if (_children != null) {
IEnumerable children = _children.Values;
foreach (BaseConfigurationRecord child in children) {
child.hlClearResultRecursive(configKey, forceEvaluatation);
}
}
}
// Returns a child record.
// Requires the hierarchy lock to be acquired (hl)
internal BaseConfigurationRecord hlGetChild(string configName) {
if (_children == null)
return null;
return (BaseConfigurationRecord) _children[configName];
}
// Adds a child record.
// Requires the hierarchy lock to be acquired (hl)
internal void hlAddChild(string configName, BaseConfigurationRecord child) {
if (_children == null) {
_children = new Hashtable(StringComparer.OrdinalIgnoreCase);
}
_children.Add(configName, child);
}
// Removes a child record.
// Requires the hierarchy lock to be acquired (hl)
internal void hlRemoveChild(string configName) {
if (_children != null) {
_children.Remove(configName);
}
}
// Removes true if a child record is needed for a
// child config path.
// Requires the hierarchy lock to be acquired (hl)
internal bool hlNeedsChildFor(string configName) {
// Always return true for root config record
if (IsRootConfig)
return true;
// Never create a child record when the parent has an exception.
if (HasInitErrors) {
return false;
}
string childConfigPath = ConfigPathUtility.Combine(_configPath, configName);
try {
using (Impersonate()) {
// check host if required
if (Host.IsConfigRecordRequired(childConfigPath)) {
return true;
}
}
}
catch {
// Don't allow frames up the stack to run exception filters while impersonated.
throw;
}
// see if there's a location
if (_flags[SupportsLocation]) {
BaseConfigurationRecord configRecord = this;
while (!configRecord.IsRootConfig) {
if (configRecord._locationSections != null) {
configRecord.ResolveLocationSections();
foreach (LocationSectionRecord locationSectionRecord in configRecord._locationSections) {
if (UrlPath.IsEqualOrSubpath(childConfigPath, locationSectionRecord.SectionXmlInfo.TargetConfigPath)) {
return true;
}
}
}
configRecord = configRecord._parent;
}
}
return false;
}
// Close the record. An explicit close is needed
// in order to stop monitoring streams used by
// this record. Stream monitors cause this record
// to be rooted in the GC heap.
//
// Note that we purposely do not cleanup the child/parent
// hierarchy. This is so that a config system which has
// a pointer to this record can still call GetSection on
// it while another thread closes it.
internal void CloseRecursive() {
if (!_flags[Closed]) {
bool doClose = false;
HybridDictionary streamInfos = null;
StreamChangeCallback callbackDelegate = null;
lock (this) {
if (!_flags[Closed]) {
_flags[Closed] = true;
doClose = true;
if (!IsLocationConfig && ConfigStreamInfo.HasStreamInfos) {
callbackDelegate = ConfigStreamInfo.CallbackDelegate;
streamInfos = ConfigStreamInfo.StreamInfos;
ConfigStreamInfo.CallbackDelegate = null;
ConfigStreamInfo.ClearStreamInfos();
}
}
}
if (doClose) {
// no hierarchy lock is needed to access _children here,
// as it has already been detached from the hierarchy tree
if (_children != null) {
foreach (BaseConfigurationRecord child in _children.Values) {
child.CloseRecursive();
}
}
if (streamInfos != null) {
foreach (StreamInfo streamInfo in streamInfos.Values) {
if (streamInfo.IsMonitored) {
Host.StopMonitoringStreamForChanges(streamInfo.StreamName, callbackDelegate);
streamInfo.IsMonitored = false;
}
}
}
}
}
}
internal string FindChangedConfigurationStream() {
BaseConfigurationRecord configRecord = this;
while (!configRecord.IsRootConfig) {
lock (configRecord) {
if (configRecord.ConfigStreamInfo.HasStreamInfos) {
foreach (StreamInfo streamInfo in configRecord.ConfigStreamInfo.StreamInfos.Values) {
if (streamInfo.IsMonitored && HasStreamChanged(streamInfo.StreamName, streamInfo.Version)) {
return streamInfo.StreamName;
}
}
}
}
configRecord = configRecord._parent;
}
return null;
}
private bool HasStreamChanged(string streamname, object lastVersion) {
object currentVersion = Host.GetStreamVersion(streamname);
if (lastVersion != null) {
return (currentVersion == null || !lastVersion.Equals(currentVersion));
}
else {
return currentVersion != null;
}
}
// RuntimeConfigurationRecord will override it in order to Assert Fulltrust before calling the provider.
// See VSWhidbey 429996.
protected virtual string CallHostDecryptSection(string encryptedXml, ProtectedConfigurationProvider protectionProvider, ProtectedConfigurationSection protectedConfig) {
return Host.DecryptSection(encryptedXml, protectionProvider, protectedConfig);
}
protected virtual XmlNode CallHostProcessRawXml(XmlNode rawXml, ConfigurationBuilder configBuilder) {
if (ConfigBuilderHost != null) {
return ConfigBuilderHost.ProcessRawXml(rawXml, configBuilder);
}
return rawXml;
}
protected virtual ConfigurationSection CallHostProcessConfigurationSection(ConfigurationSection configSection, ConfigurationBuilder configBuilder) {
if (ConfigBuilderHost != null) {
try {
return ConfigBuilderHost.ProcessConfigurationSection(configSection, configBuilder);
} catch (Exception e) {
throw ExceptionUtil.WrapAsConfigException(SR.GetString(SR.ConfigBuilder_processSection_error,
configBuilder.Name, configSection.SectionInformation.Name), e, null);
}
}
return configSection;
}
static internal string ValidateConfigBuilderAttribute(string configBuilder, IConfigErrorInfo errorInfo) {
if (String.IsNullOrEmpty(configBuilder)) {
throw new ConfigurationErrorsException(SR.GetString(SR.Config_builder_invalid_format), errorInfo);
}
return configBuilder;
}
static internal string ValidateProtectionProviderAttribute(string protectionProvider, IConfigErrorInfo errorInfo) {
if (String.IsNullOrEmpty(protectionProvider)) {
throw new ConfigurationErrorsException(SR.GetString(SR.Protection_provider_invalid_format), errorInfo);
}
return protectionProvider;
}
[SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification="Reading trusted input")]
private ConfigXmlReader DecryptConfigSection(ConfigXmlReader reader, ProtectedConfigurationProvider protectionProvider) {
ConfigXmlReader clone = reader.Clone();
IConfigErrorInfo err = (IConfigErrorInfo)clone;
string encryptedXml = null;
string clearTextXml = null;
XmlNodeType nodeType;
clone.Read();
// Save the file and line at the top of the section
string filename = err.Filename;
int lineNumber = err.LineNumber;
int sectionLineNumber = lineNumber;
if (clone.IsEmptyElement) {
throw new ConfigurationErrorsException(SR.GetString(SR.EncryptedNode_not_found), filename, lineNumber);
}
//////////////////////////////////////////////////////////
// Find the <EncryptedData> node
for (;;) {
clone.Read(); // Keep reading till we find a relavant node
nodeType = clone.NodeType;
if (nodeType == XmlNodeType.Element && clone.Name == "EncryptedData") { // Found it!
break;
}
if (nodeType == XmlNodeType.EndElement) {
throw new ConfigurationErrorsException(SR.GetString(SR.EncryptedNode_not_found), filename, lineNumber);
}
else if (nodeType != XmlNodeType.Comment && nodeType != XmlNodeType.Whitespace) {
// some other unexpected content
throw new ConfigurationErrorsException(SR.GetString(SR.EncryptedNode_is_in_invalid_format), filename, lineNumber);
}
}
//////////////////////////////////////////////////////////
// Do the decryption
// Save the line at the top of the <EncryptedData> node
lineNumber = err.LineNumber;
encryptedXml = clone.ReadOuterXml();
try {
clearTextXml = CallHostDecryptSection(encryptedXml, protectionProvider, ProtectedConfig);
} catch (Exception e) {
throw new ConfigurationErrorsException(SR.GetString(SR.Decryption_failed, protectionProvider.Name, e.Message), e, filename, lineNumber);
}
// Detect if there is any XML left over after <EncryptedData>
do {
nodeType = clone.NodeType;
if (nodeType == XmlNodeType.EndElement) {
break;
}
else if (nodeType != XmlNodeType.Comment && nodeType != XmlNodeType.Whitespace) {
// Got other unexpected content
throw new ConfigurationErrorsException(SR.GetString(SR.EncryptedNode_is_in_invalid_format), filename, lineNumber);
}
} while (clone.Read());
// Create a new reader, using the position of the original reader
return new ConfigXmlReader(clearTextXml, filename, sectionLineNumber, true);
}
[SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification="Reading trusted input")]
private ConfigXmlReader ProcessRawXml(ConfigXmlReader reader, ConfigurationBuilder configBuilder) {
IConfigErrorInfo err = (IConfigErrorInfo)reader;
XmlNode processedXml = null;
string filename = err.Filename;
int lineNumber = err.LineNumber;
try {
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load(reader);
processedXml = CallHostProcessRawXml(doc.DocumentElement, configBuilder);
}
catch (Exception e) {
// 'lineNumber' is actually off by one here for some reason. But this will be caught/rethrown
// in GetSectionXmlReader, which will add the correct line number... so long as we don't add it here.
throw ExceptionUtil.WrapAsConfigException(SR.GetString(SR.ConfigBuilder_processXml_error_short, configBuilder.Name), e, null);
}
return new ConfigXmlReader(processedXml.OuterXml, filename, lineNumber, true);
}
// ConfigContext
//
// Retrieve the context for the config
//
internal object ConfigContext
{
get
{
if (!_flags[ContextEvaluated]) {
// Retrieve context for Path
_configContext = Host.CreateConfigurationContext(ConfigPath, LocationSubPath);
_flags[ContextEvaluated] = true;
}
return _configContext;
}
}
// ThrowIfParseErrors
//
// Throw if there were parse errors detected
//
private void ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors) {
schemaErrors.ThrowIfErrors(ClassFlags[ClassIgnoreLocalErrors]);
}
// RecordSupportsLocation
//
// Does it make sense to put use location tags in this file?
// In the web case this is true at any level. In the exe case
// this is only true for machine.config (since machine.config
// can really be used for any scenario)
//
internal bool RecordSupportsLocation {
get {
return (_flags[SupportsLocation] || IsMachineConfig);
}
}
//
// Note: Some of the per-attribute encryption stuff is moved to the end of the file to minimize
// FI merging conflicts
//
const string ConfigurationBuildersSectionTypeName = "System.Configuration.ConfigurationBuildersSection, " + AssemblyRef.SystemConfiguration;
internal const string RESERVED_SECTION_CONFIGURATION_BUILDERS = "configBuilders";
Type ConfigurationBuildersSectionType = Type.GetType(ConfigurationBuildersSectionTypeName);
const string ProtectedConfigurationSectionTypeName = "System.Configuration.ProtectedConfigurationSection, " + AssemblyRef.SystemConfiguration;
internal const string RESERVED_SECTION_PROTECTED_CONFIGURATION = "configProtectedData";
internal const string Microsoft_CONFIGURATION_SECTION = ConfigurationStringConstants.WinformsApplicationConfigurationSectionName;
const string SystemConfigurationSectionTypeName = "System.Configuration.AppSettingsSection, " + AssemblyRef.SystemConfiguration;
internal static bool IsImplicitSection(string configKey) {
if (string.Equals(configKey, RESERVED_SECTION_PROTECTED_CONFIGURATION, StringComparison.Ordinal) ||
//string.Equals(configKey, RESERVED_SECTION_CONFIGURATION_BUILDERS, StringComparison.Ordinal) ||
string.Equals(configKey, Microsoft_CONFIGURATION_SECTION, StringComparison.Ordinal)) {
return true;
}
else {
return false;
}
}
//
// Add implicit sections to the factory list.
// If factoryList == null, then add to the config record's factory list.
//
private void AddImplicitSections(Hashtable factoryList) {
// Add implicit sections to the factoryList if we're under the root
// (e.g. if we're in machine.config)
if (_parent.IsRootConfig) {
if (factoryList == null) {
factoryList = EnsureFactories();
}
FactoryRecord factoryRecord = (FactoryRecord)factoryList[RESERVED_SECTION_PROTECTED_CONFIGURATION];
// If the user has mistakenly declared an implicit section, we should leave the factoryRecord
// alone because it contains the error and the error will be thrown later.
if (factoryRecord != null) {
Debug.Assert(factoryRecord.HasErrors, "If the user has mistakenly declared an implicit section, we should have recorded an error.");
}
else {
factoryList[RESERVED_SECTION_PROTECTED_CONFIGURATION] =
new FactoryRecord(
RESERVED_SECTION_PROTECTED_CONFIGURATION, // configKey
string.Empty, // group
RESERVED_SECTION_PROTECTED_CONFIGURATION, // name
ProtectedConfigurationSectionTypeName, // factoryTypeName
true, // allowLocation
ConfigurationAllowDefinition.Everywhere, // allowDefinition
ConfigurationAllowExeDefinition.MachineToApplication, // allowExeDefinition
OverrideModeSetting.SectionDefault, // overrideModeDefault
true, // restartOnExternalChanges
true, // requirePermission
true, // isFromTrustedConfig
true, // isUndeclared
null, // filename
-1); // lineNumber
}
factoryRecord = (FactoryRecord)factoryList[Microsoft_CONFIGURATION_SECTION];
// If the user has mistakenly declared an implicit section, we should leave the factoryRecord
// alone because it contains the error and the error will be thrown later.
if (factoryRecord != null)
{
Debug.Assert(factoryRecord.HasErrors, "If the user has mistakenly declared an implicit section, we should have recorded an error.");
}
else
{
factoryList[Microsoft_CONFIGURATION_SECTION] =
new FactoryRecord(
Microsoft_CONFIGURATION_SECTION, // configKey
string.Empty, // group
Microsoft_CONFIGURATION_SECTION, // name
SystemConfigurationSectionTypeName, // factoryTypeName
true, // allowLocation
ConfigurationAllowDefinition.Everywhere, // allowDefinition
ConfigurationAllowExeDefinition.MachineToApplication, // allowExeDefinition
OverrideModeSetting.SectionDefault, // overrideModeDefault
true, // restartOnExternalChanges
true, // requirePermission
true, // isFromTrustedConfig
true, // isUndeclared
null, // filename
-1); // lineNumber
}
}
}
// We reserve all attribute names starting with config or lock
internal static bool IsReservedAttributeName(string name) {
if (StringUtil.StartsWith(name, "config") ||
StringUtil.StartsWith(name, "lock")) {
return true;
}
else {
return false;
}
}
protected class ConfigRecordStreamInfo {
private bool _hasStream; // does the stream exist?
private string _streamname; // name of the stream of this record
private object _streamVersion; // version of the stream
private Encoding _encoding; // encoding of the stream
private StreamChangeCallback _callbackDelegate; // host delegate to callback to when stream has changed
private HybridDictionary _streamInfos; // streamname -> StreamInfo. It'll also contain the main stream pointed to by _streamname
internal ConfigRecordStreamInfo() {
// default encoding
_encoding = Encoding.UTF8;
}
internal bool HasStream {
get { return _hasStream; }
set { _hasStream = value; }
}
internal string StreamName {
get { return _streamname; }
set { _streamname = value; }
}
internal object StreamVersion {
get { return _streamVersion; }
set { _streamVersion = value; }
}
internal Encoding StreamEncoding {
get { return _encoding; }
set { _encoding = value; }
}
internal StreamChangeCallback CallbackDelegate {
get { return _callbackDelegate; }
set { _callbackDelegate = value; }
}
internal HybridDictionary StreamInfos {
get {
if (_streamInfos == null) {
_streamInfos = new HybridDictionary(true);
}
return _streamInfos;
}
}
internal bool HasStreamInfos {
get { return _streamInfos != null; }
}
internal void ClearStreamInfos() {
_streamInfos = null;
}
#if DBG
// For Debugging only
internal string[] Keys {
get {
string[] keys = new string[StreamInfos.Count];
StreamInfos.Keys.CopyTo(keys, 0);
return keys;
}
}
#endif
}
private class IndirectLocationInputComparer : IComparer<SectionInput> {
public int Compare(SectionInput x, SectionInput y) {
// We have to sort the indirect inputs
// 1. First by the location tag's target config path, and if they're the same,
// 2. Then by the location tag's definition config path.
//
// In the final sorted list, a child will be smaller than a parent.
Debug.Assert(x.SectionXmlInfo.ConfigKey == y.SectionXmlInfo.ConfigKey);
if (Object.ReferenceEquals(x, y)) {
// Check if they're the same object.
return 0;
}
string xTargetConfigPath = x.SectionXmlInfo.TargetConfigPath;
string yTargetConfigPath = y.SectionXmlInfo.TargetConfigPath;
// First compare using location tag's target config path:
if (UrlPath.IsSubpath(xTargetConfigPath, yTargetConfigPath)) {
// yTargetConfigPath is a child path of xTargetConfigPath, so y < x
return 1;
}
else if (UrlPath.IsSubpath(yTargetConfigPath, xTargetConfigPath)) {
// xTargetConfigPath is a child path of yTargetConfigPath, so x < y
return -1;
}
else {
// Because all indirect inputs must be pointing to nodes along a
// single branch of config hierarchy, so if the above two cases
// aren't true, then the two target config path must be equal;
// in another word, they should not be siblings.
Debug.Assert(StringUtil.EqualsIgnoreCase(yTargetConfigPath, xTargetConfigPath));
string xDefinitionConfigPath = x.SectionXmlInfo.DefinitionConfigPath;
string yDefinitionConfigPath = y.SectionXmlInfo.DefinitionConfigPath;
// Then compare using where the location tag is defined.
if (UrlPath.IsSubpath(xDefinitionConfigPath, yDefinitionConfigPath)) {
// yDefinitionConfigPath is a child path of xDefinitionConfigPath, so y < x
return 1;
}
else if (UrlPath.IsSubpath(yDefinitionConfigPath, xDefinitionConfigPath)) {
// xDefinitionConfigPath is a child path of yDefinitionConfigPath, so x < y
return -1;
}
else {
Debug.Assert(false,
"It's not possible for two location input to come from the same config file and point to the same target");
return 0;
}
}
}
}
internal Configuration CurrentConfiguration {
get {
return _configRoot.CurrentConfiguration;
}
}
internal bool TypeStringTransformerIsSet {
get {
return (CurrentConfiguration == null) ? false : CurrentConfiguration.TypeStringTransformerIsSet;
}
}
internal bool AssemblyStringTransformerIsSet {
get {
return (CurrentConfiguration == null) ? false : CurrentConfiguration.AssemblyStringTransformerIsSet;
}
}
internal System.Func<string, string> TypeStringTransformer {
get {
return (CurrentConfiguration == null) ? null : CurrentConfiguration.TypeStringTransformer;
}
}
internal System.Func<string, string> AssemblyStringTransformer {
get {
return (CurrentConfiguration == null) ? null : CurrentConfiguration.AssemblyStringTransformer;
}
}
internal FrameworkName TargetFramework {
get {
return (CurrentConfiguration == null) ? null : CurrentConfiguration.TargetFramework;
}
}
internal Stack SectionsStack {
get {
return (CurrentConfiguration == null) ? (new Stack()) : CurrentConfiguration.SectionsStack;
}
}
}
}
|