//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.ServiceModel.Configuration { using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Runtime; using System.Runtime.Diagnostics; using System.Security; using System.ServiceModel; using System.ServiceModel.Diagnostics; using System.Xml; public abstract class ServiceModelExtensionCollectionElement : ConfigurationElement, ICollection, IConfigurationContextProviderInternal where TServiceModelExtensionElement : ServiceModelExtensionElement { [Fx.Tag.SecurityNote(Critical = "Stores information used in a security decision.")] [SecurityCritical] EvaluationContextHelper contextHelper; string extensionCollectionName = null; bool modified = false; List items = null; ConfigurationPropertyCollection properties = null; internal ServiceModelExtensionCollectionElement(string extensionCollectionName) { this.extensionCollectionName = extensionCollectionName; } public TServiceModelExtensionElement this[int index] { get { return this.Items[index]; } } public TServiceModelExtensionElement this[Type extensionType] { get { if (extensionType == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("extensionType"); } if (!this.CollectionElementBaseType.IsAssignableFrom(extensionType)) { #pragma warning disable 56506 //Microsoft; Variable 'extensionType' checked for null previously throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("extensionType", SR.GetString(SR.ConfigInvalidExtensionType, extensionType.ToString(), this.CollectionElementBaseType.FullName, this.extensionCollectionName)); #pragma warning restore } TServiceModelExtensionElement retval = null; foreach (TServiceModelExtensionElement collectionElement in this) { if (null != collectionElement) { if (collectionElement.GetType() == extensionType) { retval = collectionElement; } } } return retval; } } public int Count { get { return this.Items.Count; } } bool ICollection.IsReadOnly { get { return this.IsReadOnly(); } } internal List Items { get { if (this.items == null) { this.items = new List(); } return this.items; } } protected override ConfigurationPropertyCollection Properties { get { if (this.properties == null) { this.properties = new ConfigurationPropertyCollection(); } return this.properties; } } public virtual void Add(TServiceModelExtensionElement element) { if (this.IsReadOnly()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ConfigurationErrorsException(SR.GetString(SR.ConfigReadOnly))); } if (element == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("element"); } element.ExtensionCollectionName = this.extensionCollectionName; if (this.Contains(element)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("element", SR.GetString(SR.ConfigDuplicateKey, element.ConfigurationElementName)); } else if (!this.CanAdd(element)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("element", SR.GetString(SR.ConfigElementTypeNotAllowed, element.ConfigurationElementName, this.extensionCollectionName)); } else { element.ContainingEvaluationContext = ConfigurationHelpers.GetEvaluationContext(this); ConfigurationProperty configProperty = new ConfigurationProperty(element.ConfigurationElementName, element.GetType(), null); this.Properties.Add(configProperty); this[configProperty] = element; this.Items.Add(element); this.modified = true; } } internal void AddItem(TServiceModelExtensionElement element) { if (this.IsReadOnly()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ConfigurationErrorsException(SR.GetString(SR.ConfigReadOnly))); } element.ExtensionCollectionName = this.extensionCollectionName; element.ContainingEvaluationContext = ConfigurationHelpers.GetEvaluationContext(this); this.Items.Add(element); this.modified = true; } public virtual bool CanAdd(TServiceModelExtensionElement element) { if (null == element) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("element"); } bool retval = false; Type elementType = element.GetType(); if (!this.IsReadOnly()) { if (!this.ContainsKey(elementType)) { retval = element.CanAdd(this.extensionCollectionName, ConfigurationHelpers.GetEvaluationContext(this)); } else if (DiagnosticUtility.ShouldTraceWarning) { TraceUtility.TraceEvent(TraceEventType.Warning, TraceCode.ExtensionElementAlreadyExistsInCollection, SR.GetString(SR.TraceCodeExtensionElementAlreadyExistsInCollection), this.CreateCanAddRecord(this[elementType]), this, null); } } else if (DiagnosticUtility.ShouldTraceWarning) { TraceUtility.TraceEvent(TraceEventType.Warning, TraceCode.ConfigurationIsReadOnly, SR.GetString(SR.TraceCodeConfigurationIsReadOnly), null, this, null); } return retval; } DictionaryTraceRecord CreateCanAddRecord(TServiceModelExtensionElement element) { return this.CreateCanAddRecord(element, new Dictionary(3)); } DictionaryTraceRecord CreateCanAddRecord(TServiceModelExtensionElement element, Dictionary values) { values["ElementType"] = System.Runtime.Diagnostics.DiagnosticTraceBase.XmlEncode(typeof(TServiceModelExtensionElement).AssemblyQualifiedName); values["ConfiguredSectionName"] = element.ConfigurationElementName; values["CollectionName"] = ConfigurationStrings.ExtensionsSectionPath + "/" + this.extensionCollectionName; return new DictionaryTraceRecord(values); } public void Clear() { if (this.IsReadOnly()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ConfigurationErrorsException(SR.GetString(SR.ConfigReadOnly))); } if (this.Properties.Count > 0) { this.modified = true; } List propertiesToRemove = new List(this.Items.Count); foreach (TServiceModelExtensionElement item in this.Items) { propertiesToRemove.Add(item.ConfigurationElementName); } this.Items.Clear(); foreach (string name in propertiesToRemove) { this.Properties.Remove(name); } } internal Type CollectionElementBaseType { get { return typeof(TServiceModelExtensionElement); } } public bool Contains(TServiceModelExtensionElement element) { if (element == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("element"); } return this.ContainsKey(element.GetType()); } public bool ContainsKey(Type elementType) { if (elementType == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("elementType"); } return (this[elementType] != null); } public bool ContainsKey(string elementName) { if (string.IsNullOrEmpty(elementName)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("elementName"); } bool retval = false; foreach (TServiceModelExtensionElement element in this) { if (null != element) { string configuredSectionName = element.ConfigurationElementName; if (configuredSectionName.Equals(elementName, StringComparison.Ordinal)) { retval = true; break; } } } return retval; } public void CopyTo(TServiceModelExtensionElement[] elements, int start) { if (elements == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("elements"); } if (start < 0 || start >= elements.Length) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("start", SR.GetString(SR.ConfigInvalidStartValue, elements.Length - 1, start)); } foreach (TServiceModelExtensionElement element in this) { if (null != element) { string configuredSectionName = element.ConfigurationElementName; TServiceModelExtensionElement copiedElement = this.CreateNewSection(configuredSectionName); if ((copiedElement != null) && (start < elements.Length)) { copiedElement.CopyFrom(element); elements[start] = copiedElement; ++start; } } } } /// /// Returns the extension element, or null if the type cannot be loaded in certain situations (see the code for details). /// TServiceModelExtensionElement CreateNewSection(string name) { if (this.ContainsKey(name) && !(name == ConfigurationStrings.Clear || name == ConfigurationStrings.Remove)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ConfigurationErrorsException(SR.GetString(SR.ConfigDuplicateItem, name, this.GetType().Name), this.ElementInformation.Source, this.ElementInformation.LineNumber)); } TServiceModelExtensionElement retval = null; Type elementType; ContextInformation evaluationContext = ConfigurationHelpers.GetEvaluationContext(this); try { elementType = GetExtensionType(evaluationContext, name); } catch (ConfigurationErrorsException e) { // Work-around for bug 219506@CSDMain: if the extension type cannot be loaded, we'll ignore // the exception when running in win8 app container and reading from machine.config. if (System.ServiceModel.Channels.AppContainerInfo.IsRunningInAppContainer && evaluationContext.IsMachineLevel) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); return null; } else { throw; } } if (null != elementType) { if (this.CollectionElementBaseType.IsAssignableFrom(elementType)) { retval = (TServiceModelExtensionElement)Activator.CreateInstance(elementType); retval.ExtensionCollectionName = this.extensionCollectionName; retval.ConfigurationElementName = name; retval.InternalInitializeDefault(); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ConfigurationErrorsException(SR.GetString(SR.ConfigInvalidExtensionElement, name, this.CollectionElementBaseType.FullName), this.ElementInformation.Source, this.ElementInformation.LineNumber)); } } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ConfigurationErrorsException(SR.GetString(SR.ConfigInvalidExtensionElementName, name, this.extensionCollectionName), this.ElementInformation.Source, this.ElementInformation.LineNumber)); } return retval; } [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method UnsafeLookupCollection which elevates in order to load config.", Safe = "Does not leak any config objects.")] [SecuritySafeCritical] Type GetExtensionType(ContextInformation evaluationContext, string name) { ExtensionElementCollection collection = ExtensionsSection.UnsafeLookupCollection(this.extensionCollectionName, evaluationContext); if (collection.ContainsKey(name)) { ExtensionElement element = collection[name]; Type elementType = Type.GetType(element.Type, false); if (null == elementType) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ConfigurationErrorsException(SR.GetString(SR.ConfigInvalidType, element.Type, element.Name), this.ElementInformation.Source, this.ElementInformation.LineNumber)); } return elementType; } return null; } internal void MergeWith(List parentExtensionElements) { ServiceModelExtensionCollectionElement.Merge(parentExtensionElements, this); this.Clear(); foreach (TServiceModelExtensionElement parentExtensionElement in parentExtensionElements) { this.Add(parentExtensionElement); } } static void Merge(List parentExtensionElements, IEnumerable childExtensionElements) { foreach (TServiceModelExtensionElement childExtensionElement in childExtensionElements) { if (childExtensionElement is ClearBehaviorElement) { parentExtensionElements.Clear(); } else if (childExtensionElement is RemoveBehaviorElement) { string childExtensionElementName = (childExtensionElement as RemoveBehaviorElement).Name; if (!string.IsNullOrEmpty(childExtensionElementName)) { parentExtensionElements.RemoveAll(element => element != null && element.ConfigurationElementName == childExtensionElementName); } } else { Type childExtensionElementType = childExtensionElement.GetType(); parentExtensionElements.RemoveAll(element => element != null && element.GetType() == childExtensionElementType); parentExtensionElements.Add(childExtensionElement); } } } [Fx.Tag.SecurityNote(Critical = "Uses the critical helper SetIsPresent.", Safe = "Controls how/when SetIsPresent is used, not arbitrarily callable from PT (method is protected and class is sealed).")] [SecuritySafeCritical] protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey) { SetIsPresent(); DeserializeElementCore(reader); } private void DeserializeElementCore(XmlReader reader) { if (reader.HasAttributes && 0 < reader.AttributeCount) { while (reader.MoveToNextAttribute()) { if (this.Properties.Contains(reader.Name)) { this[reader.Name] = this.Properties[reader.Name].Converter.ConvertFromString(reader.Value); } else { this.OnDeserializeUnrecognizedAttribute(reader.Name, reader.Value); } } } if (XmlNodeType.Element != reader.NodeType) { reader.MoveToElement(); } XmlReader subTree = reader.ReadSubtree(); if (subTree.Read()) { while (subTree.Read()) { if (XmlNodeType.Element == subTree.NodeType) { // Create new child element and add it to the property collection to // associate the element with an EvaluationContext. Then deserialize // XML further to set actual values. TServiceModelExtensionElement collectionElement = this.CreateNewSection(subTree.Name); if (collectionElement != null) { this.Add(collectionElement); collectionElement.DeserializeInternal(subTree, false); } } } } } [Fx.Tag.SecurityNote(Critical = "Calls ConfigurationHelpers.SetIsPresent which elevates in order to set a property.")] [SecurityCritical] void SetIsPresent() { ConfigurationHelpers.SetIsPresent(this); } public System.Collections.Generic.IEnumerator GetEnumerator() { for (int index = 0; index < this.Items.Count; ++index) { TServiceModelExtensionElement currentValue = items[index]; yield return currentValue; } } protected override bool IsModified() { bool retval = this.modified; if (!retval) { for (int i = 0; i < this.Items.Count; i++) { TServiceModelExtensionElement element = this.Items[i]; if (element.IsModifiedInternal()) { retval = true; break; } } } return retval; } protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader) { // When this is used as a DefaultCollection (i.e. CommonBehaviors) // the element names are unrecognized by the parent tag, which delegates // to the collection's OnDeserializeUnrecognizedElement. In this case, // an unrecognized element may be expected, simply try to deserialize the // element and let DeserializeElement() throw the appropriate exception if // an error is hit. this.DeserializeElement(reader, false); return true; } public bool Remove(TServiceModelExtensionElement element) { if (element == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("element"); } bool retval = false; if (this.Contains(element)) { string configuredSectionName = element.ConfigurationElementName; TServiceModelExtensionElement existingElement = (TServiceModelExtensionElement)this[element.GetType()]; this.Items.Remove(existingElement); this.Properties.Remove(configuredSectionName); this.modified = true; retval = true; } return retval; } [Fx.Tag.SecurityNote(Critical = "Accesses critical field contextHelper.")] [SecurityCritical] protected override void Reset(ConfigurationElement parentElement) { ServiceModelExtensionCollectionElement collection = (ServiceModelExtensionCollectionElement)parentElement; foreach (TServiceModelExtensionElement collectionElement in collection.Items) { this.Items.Add(collectionElement); } // Update my properties this.UpdateProperties(collection); this.contextHelper.OnReset(parentElement); base.Reset(parentElement); } protected override void ResetModified() { for (int i = 0; i < this.Items.Count; i++) { TServiceModelExtensionElement collectionElement = this.Items[i]; collectionElement.ResetModifiedInternal(); } this.modified = false; } protected void SetIsModified() { this.modified = true; } protected override void SetReadOnly() { base.SetReadOnly(); for (int i = 0; i < this.Items.Count; i++) { TServiceModelExtensionElement element = this.Items[i]; element.SetReadOnlyInternal(); } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } protected override void Unmerge(ConfigurationElement sourceElement, ConfigurationElement parentElement, ConfigurationSaveMode saveMode) { if (sourceElement == null) { return; } ServiceModelExtensionCollectionElement sourceCollectionElement = (ServiceModelExtensionCollectionElement)sourceElement; this.UpdateProperties(sourceCollectionElement); base.Unmerge(sourceElement, parentElement, saveMode); } void UpdateProperties(ServiceModelExtensionCollectionElement sourceElement) { foreach (ConfigurationProperty property in sourceElement.Properties) { if (!this.Properties.Contains(property.Name)) { this.Properties.Add(property); } } foreach (TServiceModelExtensionElement extension in this.Items) { if (extension is ClearBehaviorElement || extension is RemoveBehaviorElement) continue; string configuredSectionName = extension.ConfigurationElementName; if (!this.Properties.Contains(configuredSectionName)) { ConfigurationProperty configProperty = new ConfigurationProperty(configuredSectionName, extension.GetType(), null); this.Properties.Add(configProperty); } } } ContextInformation IConfigurationContextProviderInternal.GetEvaluationContext() { return this.EvaluationContext; } [Fx.Tag.SecurityNote(Critical = "Accesses critical field contextHelper.", Miscellaneous = "RequiresReview -- the return value will be used for a security decision -- see comment in interface definition.")] [SecurityCritical] ContextInformation IConfigurationContextProviderInternal.GetOriginalEvaluationContext() { return this.contextHelper.GetOriginalContext(this); } } }