File: System\Xml\Core\XmlReaderSettings.cs
Project: ndp\fx\src\Xml\System.Xml.csproj (System.Xml)
//------------------------------------------------------------------------------
// <copyright file="XmlReaderSettings.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System.IO;
using System.Diagnostics;
using System.Security.Permissions;
#if !SILVERLIGHT
using Microsoft.Win32;
using System.Globalization;
using System.Security;
using System.Xml.Schema;
using System.Xml.XmlConfiguration;
#endif
using System.Runtime.Versioning;
 
namespace System.Xml {
 
    // XmlReaderSettings class specifies basic features of an XmlReader.
#if !SILVERLIGHT
    [PermissionSetAttribute(SecurityAction.InheritanceDemand, Name = "FullTrust")]
#endif
    public sealed class XmlReaderSettings {
 
        //
        // Fields
        //
 
#if ASYNC || FEATURE_NETCORE
        bool useAsync;
#endif
 
 
        // Nametable
        XmlNameTable nameTable;
 
        // XmlResolver
        XmlResolver xmlResolver;
 
        // Text settings
        int lineNumberOffset;
        int linePositionOffset;
 
        // Conformance settings
        ConformanceLevel conformanceLevel;
        bool checkCharacters;
 
        long maxCharactersInDocument;
        long maxCharactersFromEntities;
 
        // Filtering settings
        bool ignoreWhitespace;
        bool ignorePIs;
        bool ignoreComments;
 
        // security settings
        DtdProcessing dtdProcessing;
 
#if !SILVERLIGHT
        //Validation settings
        ValidationType validationType;
        XmlSchemaValidationFlags validationFlags;
        XmlSchemaSet schemas;
        ValidationEventHandler valEventHandler;
#endif
 
        // other settings
        bool closeInput;
 
        // read-only flag
        bool isReadOnly;
 
        //
        // Constructor
        //
        public XmlReaderSettings() {
            Initialize();
        }
 
#if !FEATURE_LEGACYNETCF
        // introduced for supporting design-time loading of phone assemblies
        [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)]
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
#endif
        public XmlReaderSettings(XmlResolver resolver) {
            Initialize(resolver);
        }
 
        //
        // Properties
        //
 
#if ASYNC || FEATURE_NETCORE
        public bool Async {
            get {
                return useAsync;
            }
            set {
                CheckReadOnly("Async");
                useAsync = value;
            }
        }
#endif
 
        // Nametable
        public XmlNameTable NameTable {
            get {
                return nameTable;
            }
            set {
                CheckReadOnly("NameTable");
                nameTable = value;
            }
        }
 
#if !SILVERLIGHT
        // XmlResolver
        internal bool IsXmlResolverSet {
            get;
            set; // keep set internal as we need to call it from the schema validation code
        }
#endif
 
        public XmlResolver XmlResolver {
            set {
                CheckReadOnly("XmlResolver");
                xmlResolver = value;
#if !SILVERLIGHT
                IsXmlResolverSet = true;
#endif
            }
        }
 
        internal XmlResolver GetXmlResolver() {
            return xmlResolver;
        }
 
#if !SILVERLIGHT
        //This is used by get XmlResolver in Xsd.
        //Check if the config set to prohibit default resovler
        //notice we must keep GetXmlResolver() to avoid dead lock when init System.Config.ConfigurationManager
        internal XmlResolver GetXmlResolver_CheckConfig() { 
            if (System.Xml.XmlConfiguration.XmlReaderSection.ProhibitDefaultUrlResolver && !IsXmlResolverSet)
                return null;
            else
                return xmlResolver;
        }
#endif
 
        // Text settings
        public int LineNumberOffset {
            get {
                return lineNumberOffset;
            }
            set {
                CheckReadOnly("LineNumberOffset");
                lineNumberOffset = value;
            }
        }
 
        public int LinePositionOffset {
            get {
                return linePositionOffset;
            }
            set {
                CheckReadOnly("LinePositionOffset");
                linePositionOffset = value;
            }
        }
 
        // Conformance settings
        public ConformanceLevel ConformanceLevel {
            get {
                return conformanceLevel;
            }
            set {
                CheckReadOnly("ConformanceLevel");
 
                if ((uint)value > (uint)ConformanceLevel.Document) {
                    throw new ArgumentOutOfRangeException("value");
                }
                conformanceLevel = value;
            }
        }
 
        public bool CheckCharacters {
            get {
                return checkCharacters;
            }
            set {
                CheckReadOnly("CheckCharacters");
                checkCharacters = value;
            }
        }
 
        public long MaxCharactersInDocument {
            get {
                return maxCharactersInDocument;
            }
            set {
                CheckReadOnly("MaxCharactersInDocument");
                if (value < 0) {
                    throw new ArgumentOutOfRangeException("value");
                }
                maxCharactersInDocument = value;
            }
        }
 
        public long MaxCharactersFromEntities {
            get {
                return maxCharactersFromEntities;
            }
            set {
                CheckReadOnly("MaxCharactersFromEntities");
                if (value < 0) {
                    throw new ArgumentOutOfRangeException("value");
                }
                maxCharactersFromEntities = value;
            }
        }
 
        // Filtering settings
        public bool IgnoreWhitespace {
            get {
                return ignoreWhitespace;
            }
            set {
                CheckReadOnly("IgnoreWhitespace");
                ignoreWhitespace = value;
            }
        }
 
        public bool IgnoreProcessingInstructions {
            get {
                return ignorePIs;
            }
            set {
                CheckReadOnly("IgnoreProcessingInstructions");
                ignorePIs = value;
            }
        }
 
        public bool IgnoreComments {
            get {
                return ignoreComments;
            }
            set {
                CheckReadOnly("IgnoreComments");
                ignoreComments = value;
            }
        }
 
#if !SILVERLIGHT
        [Obsolete("Use XmlReaderSettings.DtdProcessing property instead.")]
        public bool ProhibitDtd {
            get {
                return dtdProcessing == DtdProcessing.Prohibit;
            }
            set {
                CheckReadOnly("ProhibitDtd");
                dtdProcessing = value ? DtdProcessing.Prohibit : DtdProcessing.Parse;
            }
        }
#endif
 
        public DtdProcessing DtdProcessing {
            get {
                return dtdProcessing;
            }
            set {
                CheckReadOnly("DtdProcessing");
 
                if ((uint)value > (uint)DtdProcessing.Parse) {
                    throw new ArgumentOutOfRangeException("value");
                }
                dtdProcessing = value;
            }
        }
 
        public bool CloseInput {
            get {
                return closeInput;
            }
            set {
                CheckReadOnly("CloseInput");
                closeInput = value;
            }
        }
 
#if !SILVERLIGHT
        public ValidationType ValidationType {
            get {
                return validationType;
            }
            set {
                CheckReadOnly("ValidationType");
 
                if ((uint)value > (uint)ValidationType.Schema) {
                    throw new ArgumentOutOfRangeException("value");
                }
                validationType = value;
            }
        }
 
        public XmlSchemaValidationFlags ValidationFlags {
            get {
                return validationFlags;
            }
            set {
                CheckReadOnly("ValidationFlags");
 
                if ((uint)value > (uint)(XmlSchemaValidationFlags.ProcessInlineSchema | XmlSchemaValidationFlags.ProcessSchemaLocation |
                                           XmlSchemaValidationFlags.ReportValidationWarnings | XmlSchemaValidationFlags.ProcessIdentityConstraints |
                                           XmlSchemaValidationFlags.AllowXmlAttributes)) {
                    throw new ArgumentOutOfRangeException("value");
                }
                validationFlags = value;
            }
        }
 
        public XmlSchemaSet Schemas {
            get {
                if (schemas == null) {
                    schemas = new XmlSchemaSet();
                }
                return schemas;
            }
            set {
                CheckReadOnly("Schemas");
                schemas = value;
            }
        }
 
        public event ValidationEventHandler ValidationEventHandler {
            add {
                CheckReadOnly("ValidationEventHandler");
                valEventHandler += value;
            }
            remove {
                CheckReadOnly("ValidationEventHandler");
                valEventHandler -= value;
            }
        }
#endif
 
        //
        // Public methods
        //
        public void Reset() {
            CheckReadOnly("Reset");
            Initialize();
        }
 
        public XmlReaderSettings Clone() {
            XmlReaderSettings clonedSettings = this.MemberwiseClone() as XmlReaderSettings;
            clonedSettings.ReadOnly = false;
            return clonedSettings;
        }
 
        //
        // Internal methods
        //
#if !SILVERLIGHT
        internal ValidationEventHandler GetEventHandler() {
            return valEventHandler;
        }
#endif
 
 
#if !SILVERLIGHT
        [ResourceConsumption(ResourceScope.Machine)]
        [ResourceExposure(ResourceScope.Machine)]
#endif
        internal XmlReader CreateReader(String inputUri, XmlParserContext inputContext) {
            if (inputUri == null) {
                throw new ArgumentNullException("inputUri");
            }
            if (inputUri.Length == 0) {
                throw new ArgumentException(Res.GetString(Res.XmlConvert_BadUri), "inputUri");
            }
 
            // resolve and open the url
            XmlResolver tmpResolver = this.GetXmlResolver();
            if (tmpResolver == null) {
                tmpResolver = CreateDefaultResolver();
            }
 
            // create text XML reader
            XmlReader reader = new XmlTextReaderImpl(inputUri, this, inputContext, tmpResolver);
 
#if !SILVERLIGHT
            // wrap with validating reader
            if (this.ValidationType != ValidationType.None) {
                reader = AddValidation(reader);
            }
#endif
 
#if ASYNC
            if (useAsync) {
                reader = XmlAsyncCheckReader.CreateAsyncCheckWrapper(reader);
            }
#endif
            return reader;
        }
 
        internal XmlReader CreateReader(Stream input, Uri baseUri, string baseUriString, XmlParserContext inputContext) {
            if (input == null) {
                throw new ArgumentNullException("input");
            }
            if (baseUriString == null) {
                if (baseUri == null) {
                    baseUriString = string.Empty;
                }
                else {
                    baseUriString = baseUri.ToString();
                }
            }
 
            // create text XML reader
            XmlReader reader = new XmlTextReaderImpl(input, null, 0, this, baseUri, baseUriString, inputContext, closeInput);
 
#if !SILVERLIGHT
            // wrap with validating reader
            if (this.ValidationType != ValidationType.None) {
                reader = AddValidation(reader);
            }
#endif
 
#if ASYNC
            if (useAsync) {
                reader = XmlAsyncCheckReader.CreateAsyncCheckWrapper(reader);
            }
#endif
 
            return reader;
        }
 
        internal XmlReader CreateReader(TextReader input, string baseUriString, XmlParserContext inputContext) {
            if (input == null) {
                throw new ArgumentNullException("input");
            }
            if (baseUriString == null) {
                baseUriString = string.Empty;
            }
 
            // create xml text reader
            XmlReader reader = new XmlTextReaderImpl(input, this, baseUriString, inputContext);
 
#if !SILVERLIGHT
            // wrap with validating reader
            if (this.ValidationType != ValidationType.None) {
                reader = AddValidation(reader);
            }
#endif
 
#if ASYNC
            if (useAsync) {
                reader = XmlAsyncCheckReader.CreateAsyncCheckWrapper(reader);
            }
#endif
 
            return reader;
        }
 
        internal XmlReader CreateReader(XmlReader reader) {
            if (reader == null) {
                throw new ArgumentNullException("reader");
            }
#if !ASYNC || SILVERLIGHT || FEATURE_NETCORE
            // wrap with conformance layer (if needed)
            return AddConformanceWrapper(reader);
#else
            return AddValidationAndConformanceWrapper(reader);
#endif // !ASYNC || SILVERLIGHT || FEATURE_NETCORE
        }
 
        internal bool ReadOnly {
            get {
                return isReadOnly;
            }
            set {
                isReadOnly = value;
            }
        }
 
        void CheckReadOnly(string propertyName) {
            if (isReadOnly) {
                throw new XmlException(Res.Xml_ReadOnlyProperty, this.GetType().Name + '.' + propertyName);
            }
        }
 
        //
        // Private methods
        //
        void Initialize() {
            Initialize(null);
        }
 
        void Initialize(XmlResolver resolver) {
            nameTable = null;
#if !SILVERLIGHT
            if (!EnableLegacyXmlSettings())
            {
                xmlResolver = resolver;
                // limit the entity resolving to 10 million character. the caller can still
                // override it to any other value or set it to zero for unlimiting it
                maxCharactersFromEntities = (long) 1e7;
            }
            else
#endif            
            {
                xmlResolver = (resolver == null ? CreateDefaultResolver() : resolver);
                maxCharactersFromEntities = 0;
            }
            lineNumberOffset = 0;
            linePositionOffset = 0;
            checkCharacters = true;
            conformanceLevel = ConformanceLevel.Document;
 
            ignoreWhitespace = false;
            ignorePIs = false;
            ignoreComments = false;
            dtdProcessing = DtdProcessing.Prohibit;
            closeInput = false;
 
            maxCharactersInDocument = 0;
 
#if !SILVERLIGHT
            schemas = null;
            validationType = ValidationType.None;
            validationFlags = XmlSchemaValidationFlags.ProcessIdentityConstraints;
            validationFlags |= XmlSchemaValidationFlags.AllowXmlAttributes;
#endif
 
#if ASYNC || FEATURE_NETCORE
            useAsync = false;
#endif
 
            isReadOnly = false;
#if !SILVERLIGHT
            IsXmlResolverSet = false;
#endif
        }
 
        static XmlResolver CreateDefaultResolver() {
#if SILVERLIGHT
            if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_1)
                return new XmlSystemPathResolver();
            return new XmlXapResolver();
#else
            return new XmlUrlResolver();
#endif
        }
 
#if !SILVERLIGHT
        internal XmlReader AddValidation(XmlReader reader) {
            if (this.validationType == ValidationType.Schema) {
                XmlResolver resolver = GetXmlResolver_CheckConfig();
 
                if (resolver == null &&
                    !this.IsXmlResolverSet &&
                    !EnableLegacyXmlSettings())
                {
                    resolver = new XmlUrlResolver();
                }
                reader = new XsdValidatingReader(reader, resolver, this);
            }
            else if (this.validationType == ValidationType.DTD) {
                reader = CreateDtdValidatingReader(reader);
            }
            return reader;
        }
 
        private XmlReader AddValidationAndConformanceWrapper(XmlReader reader) {
            // wrap with DTD validating reader
            if (this.validationType == ValidationType.DTD) {
                reader = CreateDtdValidatingReader(reader);
            }
            // add conformance checking (must go after DTD validation because XmlValidatingReader works only on XmlTextReader),
            // but before XSD validation because of typed value access
            reader = AddConformanceWrapper(reader);
 
            if (this.validationType == ValidationType.Schema) {
                reader = new XsdValidatingReader(reader, GetXmlResolver_CheckConfig(), this);
            }
            return reader;
        }
 
        private XmlValidatingReaderImpl CreateDtdValidatingReader(XmlReader baseReader) {
            return new XmlValidatingReaderImpl(baseReader, this.GetEventHandler(), (this.ValidationFlags & XmlSchemaValidationFlags.ProcessIdentityConstraints) != 0);
        }
#endif // !SILVERLIGHT
 
        internal XmlReader AddConformanceWrapper(XmlReader baseReader) {
            XmlReaderSettings baseReaderSettings = baseReader.Settings;
            bool checkChars = false;
            bool noWhitespace = false;
            bool noComments = false;
            bool noPIs = false;
            DtdProcessing dtdProc = (DtdProcessing)(-1);
            bool needWrap = false;
 
            if (baseReaderSettings == null) {
 
#pragma warning disable 618
 
#if SILVERLIGHT
                // Starting from Windows phone 8.1 (TargetsAtLeast_Desktop_V4_5_1) we converge with the desktop behavior so we'll let the reader 
                // not throw exception if has different conformance level than Auto.
                if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_1) {
                    if (this.conformanceLevel != ConformanceLevel.Auto && this.conformanceLevel != XmlReader.GetV1ConformanceLevel(baseReader)) {
                        throw new InvalidOperationException(Res.GetString(Res.Xml_IncompatibleConformanceLevel, this.conformanceLevel.ToString()));
                    }
                } else if (this.conformanceLevel != ConformanceLevel.Auto) {
                    throw new InvalidOperationException(Res.GetString(Res.Xml_IncompatibleConformanceLevel, this.conformanceLevel.ToString()));
                }
#else
                                                if (this.conformanceLevel != ConformanceLevel.Auto && this.conformanceLevel != XmlReader.GetV1ConformanceLevel(baseReader)) {
                    throw new InvalidOperationException(Res.GetString(Res.Xml_IncompatibleConformanceLevel, this.conformanceLevel.ToString()));
                }
#endif
 
#if !SILVERLIGHT
                // get the V1 XmlTextReader ref
                XmlTextReader v1XmlTextReader = baseReader as XmlTextReader;
                if (v1XmlTextReader == null) {
                    XmlValidatingReader vr = baseReader as XmlValidatingReader;
                    if (vr != null) {
                        v1XmlTextReader = (XmlTextReader)vr.Reader;
                    }
                }
#endif
 
                // assume the V1 readers already do all conformance checking; 
                // wrap only if IgnoreWhitespace, IgnoreComments, IgnoreProcessingInstructions or ProhibitDtd is true;
                if (this.ignoreWhitespace) {
                    WhitespaceHandling wh = WhitespaceHandling.All;
#if !SILVERLIGHT
                    // special-case our V1 readers to see if whey already filter whitespaces
                    if (v1XmlTextReader != null) {
                        wh = v1XmlTextReader.WhitespaceHandling;
                    }
#endif
                    if (wh == WhitespaceHandling.All) {
                        noWhitespace = true;
                        needWrap = true;
                    }
                }
                if (this.ignoreComments) {
                    noComments = true;
                    needWrap = true;
                }
                if (this.ignorePIs) {
                    noPIs = true;
                    needWrap = true;
                }
                // DTD processing
                DtdProcessing baseDtdProcessing = DtdProcessing.Parse;
#if !SILVERLIGHT
                if (v1XmlTextReader != null) {
                    baseDtdProcessing = v1XmlTextReader.DtdProcessing;
                }
#endif
                if ((this.dtdProcessing == DtdProcessing.Prohibit && baseDtdProcessing != DtdProcessing.Prohibit) ||
                    (this.dtdProcessing == DtdProcessing.Ignore && baseDtdProcessing == DtdProcessing.Parse)) {
                    dtdProc = this.dtdProcessing;
                    needWrap = true;
                }
#pragma warning restore 618
            }
            else {
                if (this.conformanceLevel != baseReaderSettings.ConformanceLevel && this.conformanceLevel != ConformanceLevel.Auto) {
                    throw new InvalidOperationException(Res.GetString(Res.Xml_IncompatibleConformanceLevel, this.conformanceLevel.ToString()));
                }
                if (this.checkCharacters && !baseReaderSettings.CheckCharacters) {
                    checkChars = true;
                    needWrap = true;
                }
                if (this.ignoreWhitespace && !baseReaderSettings.IgnoreWhitespace) {
                    noWhitespace = true;
                    needWrap = true;
                }
                if (this.ignoreComments && !baseReaderSettings.IgnoreComments) {
                    noComments = true;
                    needWrap = true;
                }
                if (this.ignorePIs && !baseReaderSettings.IgnoreProcessingInstructions) {
                    noPIs = true;
                    needWrap = true;
                }
 
                if ((this.dtdProcessing == DtdProcessing.Prohibit && baseReaderSettings.DtdProcessing != DtdProcessing.Prohibit) ||
                    (this.dtdProcessing == DtdProcessing.Ignore && baseReaderSettings.DtdProcessing == DtdProcessing.Parse)) {
                    dtdProc = this.dtdProcessing;
                    needWrap = true;
                }
            }
 
            if (needWrap) {
                IXmlNamespaceResolver readerAsNSResolver = baseReader as IXmlNamespaceResolver;
                if (readerAsNSResolver != null) {
                    return new XmlCharCheckingReaderWithNS(baseReader, readerAsNSResolver, checkChars, noWhitespace, noComments, noPIs, dtdProc);
                }
                else {
                    return new XmlCharCheckingReader(baseReader, checkChars, noWhitespace, noComments, noPIs, dtdProc);
                }
            }
            else {
                return baseReader;
            }
        }
 
#if !SILVERLIGHT
        private static bool? s_enableLegacyXmlSettings = null;
 
        static internal bool EnableLegacyXmlSettings()
        {
            if (s_enableLegacyXmlSettings.HasValue)
            {
                return s_enableLegacyXmlSettings.Value;
            }
 
            if (!System.Xml.BinaryCompatibility.TargetsAtLeast_Desktop_V4_5_2)
            {
                s_enableLegacyXmlSettings = true;
                return s_enableLegacyXmlSettings.Value;
            }
 
            bool enableSettings = false; // default value
            if (!ReadSettingsFromRegistry(Registry.LocalMachine, ref enableSettings))
            {
                // still ok if this call return false too as we'll use the default value which is false
                ReadSettingsFromRegistry(Registry.CurrentUser, ref enableSettings);
            }
 
            s_enableLegacyXmlSettings = enableSettings;
            return s_enableLegacyXmlSettings.Value;
        }
 
        [RegistryPermission(SecurityAction.Assert, Unrestricted = true)]
        [SecuritySafeCritical]
        private static bool ReadSettingsFromRegistry(RegistryKey hive, ref bool value)
        {
            const string regValueName = "EnableLegacyXmlSettings";
            const string regValuePath = @"SOFTWARE\Microsoft\.NETFramework\XML";
 
            try
            {                                                                     
                using (RegistryKey xmlRegKey = hive.OpenSubKey(regValuePath, false))
                {
                    if (xmlRegKey != null)
                    {
                        if (xmlRegKey.GetValueKind(regValueName) == RegistryValueKind.DWord)
                        {
                            value = ((int)xmlRegKey.GetValue(regValueName)) == 1;
                            return true;
                        }
                    }
                }
            }
            catch { /* use the default if we couldn't read the key */ }
 
            return false;
        }
 
#endif // SILVERLIGHT
        
    }
}