//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft //------------------------------------------------------------------------------ namespace System.Xml.Schema { using System.IO; using System.Text; using System.Collections; using System.Diagnostics; using System.Globalization; using System.Runtime.Versioning; #pragma warning disable 618 internal sealed class XdrValidator : BaseValidator { private const int STACK_INCREMENT = 10; private HWStack validationStack; // validaton contexts private Hashtable attPresence; private XmlQualifiedName name = XmlQualifiedName.Empty; private XmlNamespaceManager nsManager; private bool isProcessContents = false; private Hashtable IDs; private IdRefNode idRefListHead; private Parser inlineSchemaParser = null; private const string x_schema = "x-schema:"; internal XdrValidator(BaseValidator validator) : base(validator) { Init(); } internal XdrValidator(XmlValidatingReaderImpl reader, XmlSchemaCollection schemaCollection, IValidationEventHandling eventHandling) : base(reader, schemaCollection, eventHandling) { Init(); } private void Init() { nsManager = reader.NamespaceManager; if (nsManager == null) { nsManager = new XmlNamespaceManager(NameTable); isProcessContents = true; } validationStack = new HWStack(STACK_INCREMENT); textValue = new StringBuilder(); name = XmlQualifiedName.Empty; attPresence = new Hashtable(); Push(XmlQualifiedName.Empty); schemaInfo = new SchemaInfo(); checkDatatype = false; } public override void Validate() { if (IsInlineSchemaStarted) { ProcessInlineSchema(); } else { switch (reader.NodeType) { case XmlNodeType.Element: ValidateElement(); if (reader.IsEmptyElement) { goto case XmlNodeType.EndElement; } break; case XmlNodeType.Whitespace: ValidateWhitespace(); break; case XmlNodeType.Text: // text inside a node case XmlNodeType.CDATA: // case XmlNodeType.SignificantWhitespace: ValidateText(); break; case XmlNodeType.EndElement: ValidateEndElement(); break; } } } private void ValidateElement() { elementName.Init(reader.LocalName, XmlSchemaDatatype.XdrCanonizeUri(reader.NamespaceURI, NameTable, SchemaNames)); ValidateChildElement(); if (SchemaNames.IsXDRRoot(elementName.Name, elementName.Namespace) && reader.Depth > 0) { inlineSchemaParser = new Parser(SchemaType.XDR, NameTable, SchemaNames, EventHandler); inlineSchemaParser.StartParsing(reader, null); inlineSchemaParser.ParseReaderNode(); } else { ProcessElement(); } } private void ValidateChildElement() { if (context.NeedValidateChildren) { int errorCode = 0; context.ElementDecl.ContentValidator.ValidateElement(elementName, context, out errorCode); if (errorCode < 0) { XmlSchemaValidator.ElementValidationError(elementName, context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null); } } } private bool IsInlineSchemaStarted { get { return inlineSchemaParser != null; } } private void ProcessInlineSchema() { if (!inlineSchemaParser.ParseReaderNode()) { // Done inlineSchemaParser.FinishParsing(); SchemaInfo xdrSchema = inlineSchemaParser.XdrSchema; if (xdrSchema != null && xdrSchema.ErrorCount == 0) { foreach(string inlineNS in xdrSchema.TargetNamespaces.Keys) { if (!this.schemaInfo.HasSchema(inlineNS)) { schemaInfo.Add(xdrSchema, EventHandler); SchemaCollection.Add(inlineNS, xdrSchema, null, false); break; } } } inlineSchemaParser = null; } } private void ProcessElement() { Push(elementName); if (isProcessContents) { nsManager.PopScope(); } context.ElementDecl = ThoroughGetElementDecl(); if (context.ElementDecl != null) { ValidateStartElement(); ValidateEndStartElement(); context.NeedValidateChildren = true; context.ElementDecl.ContentValidator.InitValidation(context); } } private void ValidateEndElement() { if (isProcessContents) { nsManager.PopScope(); } if (context.ElementDecl != null) { if (context.NeedValidateChildren) { if(!context.ElementDecl.ContentValidator.CompleteValidation(context)) { XmlSchemaValidator.CompleteValidationError(context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null); } } if (checkDatatype) { string stringValue = !hasSibling ? textString : textValue.ToString(); // only for identity-constraint exception reporting CheckValue(stringValue, null); checkDatatype = false; textValue.Length = 0; // cleanup textString = string.Empty; } } Pop(); } // SxS: This method processes resource names read from the source document and does not expose // any resources to the caller. It is fine to suppress the SxS warning. [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] [ResourceExposure(ResourceScope.None)] private SchemaElementDecl ThoroughGetElementDecl() { if (reader.Depth == 0) { LoadSchema(string.Empty); } if (reader.MoveToFirstAttribute()) { do { string objectNs = reader.NamespaceURI; string objectName = reader.LocalName; if (Ref.Equal(objectNs, SchemaNames.NsXmlNs)) { LoadSchema(reader.Value); if (isProcessContents) { nsManager.AddNamespace(reader.Prefix.Length == 0 ? string.Empty : reader.LocalName, reader.Value); } } if ( Ref.Equal(objectNs, SchemaNames.QnDtDt.Namespace) && Ref.Equal(objectName, SchemaNames.QnDtDt.Name) ) { reader.SchemaTypeObject = XmlSchemaDatatype.FromXdrName(reader.Value); } } while(reader.MoveToNextAttribute()); reader.MoveToElement(); } SchemaElementDecl elementDecl = schemaInfo.GetElementDecl(elementName); if(elementDecl == null) { if(schemaInfo.TargetNamespaces.ContainsKey(context.Namespace)) { SendValidationEvent(Res.Sch_UndeclaredElement, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); } } return elementDecl; } private void ValidateStartElement() { if (context.ElementDecl != null) { if (context.ElementDecl.SchemaType != null) { reader.SchemaTypeObject = context.ElementDecl.SchemaType; } else { reader.SchemaTypeObject = context.ElementDecl.Datatype; } if (reader.IsEmptyElement && !context.IsNill && context.ElementDecl.DefaultValueTyped != null) { reader.TypedValueObject = context.ElementDecl.DefaultValueTyped; context.IsNill = true; // reusing IsNill } if (this.context.ElementDecl.HasRequiredAttribute) { attPresence.Clear(); } } if (reader.MoveToFirstAttribute()) { do { if ((object)reader.NamespaceURI == (object)SchemaNames.NsXmlNs) { continue; } try { reader.SchemaTypeObject = null; SchemaAttDef attnDef = schemaInfo.GetAttributeXdr(context.ElementDecl, QualifiedName(reader.LocalName, reader.NamespaceURI)); if (attnDef != null) { if (context.ElementDecl != null && context.ElementDecl.HasRequiredAttribute) { attPresence.Add(attnDef.Name, attnDef); } reader.SchemaTypeObject = (attnDef.SchemaType != null) ? (object)attnDef.SchemaType : (object)attnDef.Datatype; if (attnDef.Datatype != null) { string attributeValue = reader.Value; // need to check the contents of this attribute to make sure // it is valid according to the specified attribute type. CheckValue(attributeValue, attnDef); } } } catch (XmlSchemaException e) { e.SetSource(reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); SendValidationEvent(e); } } while(reader.MoveToNextAttribute()); reader.MoveToElement(); } } private void ValidateEndStartElement() { if (context.ElementDecl.HasDefaultAttribute) { for (int i = 0; i < context.ElementDecl.DefaultAttDefs.Count; ++i) { reader.AddDefaultAttribute((SchemaAttDef)context.ElementDecl.DefaultAttDefs[i]); } } if (context.ElementDecl.HasRequiredAttribute) { try { context.ElementDecl.CheckAttributes(attPresence, reader.StandAlone); } catch (XmlSchemaException e) { e.SetSource(reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); SendValidationEvent(e); } } if (context.ElementDecl.Datatype != null) { checkDatatype = true; hasSibling = false; textString = string.Empty; textValue.Length = 0; } } [ResourceConsumption(ResourceScope.Machine)] [ResourceExposure(ResourceScope.Machine)] private void LoadSchemaFromLocation(string uri) { // is x-schema if (!XdrBuilder.IsXdrSchema(uri)) { return; } string url = uri.Substring(x_schema.Length); XmlReader reader = null; SchemaInfo xdrSchema = null; try { Uri ruri = this.XmlResolver.ResolveUri(BaseUri, url); Stream stm = (Stream)this.XmlResolver.GetEntity(ruri,null,null); reader = new XmlTextReader(ruri.ToString(), stm, NameTable); ((XmlTextReader)reader).XmlResolver = this.XmlResolver; Parser parser = new Parser(SchemaType.XDR, NameTable, SchemaNames, EventHandler); parser.XmlResolver = this.XmlResolver; parser.Parse(reader, uri); while(reader.Read());// wellformness check xdrSchema = parser.XdrSchema; } catch(XmlSchemaException e) { SendValidationEvent(Res.Sch_CannotLoadSchema, new string[] {uri, e.Message}, XmlSeverityType.Error); } catch(Exception e) { SendValidationEvent(Res.Sch_CannotLoadSchema, new string[] {uri, e.Message}, XmlSeverityType.Warning); } finally { if (reader != null) { reader.Close(); } } if (xdrSchema != null && xdrSchema.ErrorCount == 0) { schemaInfo.Add(xdrSchema, EventHandler); SchemaCollection.Add(uri, xdrSchema, null, false); } } [ResourceConsumption(ResourceScope.Machine)] [ResourceExposure(ResourceScope.Machine)] private void LoadSchema(string uri) { if (this.schemaInfo.TargetNamespaces.ContainsKey(uri)) { return; } if (this.XmlResolver == null) { return; } SchemaInfo schemaInfo = null; if (SchemaCollection != null) schemaInfo = SchemaCollection.GetSchemaInfo(uri); if (schemaInfo != null) { if(schemaInfo.SchemaType != SchemaType.XDR) { throw new XmlException(Res.Xml_MultipleValidaitonTypes, string.Empty, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition); } this.schemaInfo.Add(schemaInfo, EventHandler); return; } LoadSchemaFromLocation(uri); } private bool HasSchema { get { return schemaInfo.SchemaType != SchemaType.None;}} public override bool PreserveWhitespace { get { return context.ElementDecl != null ? context.ElementDecl.ContentValidator.PreserveWhitespace : false; } } void ProcessTokenizedType( XmlTokenizedType ttype, string name ) { switch(ttype) { case XmlTokenizedType.ID: if (FindId(name) != null) { SendValidationEvent(Res.Sch_DupId, name); } else { AddID(name, context.LocalName); } break; case XmlTokenizedType.IDREF: object p = FindId(name); if (p == null) { // add it to linked list to check it later idRefListHead = new IdRefNode(idRefListHead, name, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition); } break; case XmlTokenizedType.ENTITY: ProcessEntity(schemaInfo, name, this, EventHandler, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); break; default: break; } } public override void CompleteValidation() { if (HasSchema) { CheckForwardRefs(); } else { SendValidationEvent(new XmlSchemaException(Res.Xml_NoValidation, string.Empty), XmlSeverityType.Warning); } } private void CheckValue( string value, SchemaAttDef attdef ) { try { reader.TypedValueObject = null; bool isAttn = attdef != null; XmlSchemaDatatype dtype = isAttn ? attdef.Datatype : context.ElementDecl.Datatype; if (dtype == null) { return; // no reason to check } if (dtype.TokenizedType != XmlTokenizedType.CDATA) { value = value.Trim(); } if (value.Length == 0) { return; // don't need to check } object typedValue = dtype.ParseValue(value, NameTable, nsManager); reader.TypedValueObject = typedValue; // Check special types XmlTokenizedType ttype = dtype.TokenizedType; if (ttype == XmlTokenizedType.ENTITY || ttype == XmlTokenizedType.ID || ttype == XmlTokenizedType.IDREF) { if (dtype.Variety == XmlSchemaDatatypeVariety.List) { string[] ss = (string[])typedValue; for (int i = 0; i < ss.Length; ++i) { ProcessTokenizedType(dtype.TokenizedType, ss[i]); } } else { ProcessTokenizedType(dtype.TokenizedType, (string)typedValue); } } SchemaDeclBase decl = isAttn ? (SchemaDeclBase)attdef : (SchemaDeclBase)context.ElementDecl; if (decl.MaxLength != uint.MaxValue) { if(value.Length > decl.MaxLength) { SendValidationEvent(Res.Sch_MaxLengthConstraintFailed, value); } } if (decl.MinLength != uint.MaxValue) { if(value.Length < decl.MinLength) { SendValidationEvent(Res.Sch_MinLengthConstraintFailed, value); } } if (decl.Values != null && !decl.CheckEnumeration(typedValue)) { if (dtype.TokenizedType == XmlTokenizedType.NOTATION) { SendValidationEvent(Res.Sch_NotationValue, typedValue.ToString()); } else { SendValidationEvent(Res.Sch_EnumerationValue, typedValue.ToString()); } } if (!decl.CheckValue(typedValue)) { if (isAttn) { SendValidationEvent(Res.Sch_FixedAttributeValue, attdef.Name.ToString()); } else { SendValidationEvent(Res.Sch_FixedElementValue, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); } } } catch (XmlSchemaException) { if (attdef != null) { SendValidationEvent(Res.Sch_AttributeValueDataType, attdef.Name.ToString()); } else { SendValidationEvent(Res.Sch_ElementValueDataType, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); } } } public static void CheckDefaultValue( string value, SchemaAttDef attdef, SchemaInfo sinfo, XmlNamespaceManager nsManager, XmlNameTable NameTable, object sender, ValidationEventHandler eventhandler, string baseUri, int lineNo, int linePos ) { try { XmlSchemaDatatype dtype = attdef.Datatype; if (dtype == null) { return; // no reason to check } if (dtype.TokenizedType != XmlTokenizedType.CDATA) { value = value.Trim(); } if (value.Length == 0) { return; // don't need to check } object typedValue = dtype.ParseValue(value, NameTable, nsManager); // Check special types XmlTokenizedType ttype = dtype.TokenizedType; if (ttype == XmlTokenizedType.ENTITY) { if (dtype.Variety == XmlSchemaDatatypeVariety.List) { string[] ss = (string[])typedValue; for (int i = 0; i < ss.Length; ++i) { ProcessEntity(sinfo, ss[i], sender, eventhandler, baseUri, lineNo, linePos); } } else { ProcessEntity(sinfo, (string)typedValue, sender, eventhandler, baseUri, lineNo, linePos); } } else if (ttype == XmlTokenizedType.ENUMERATION) { if (!attdef.CheckEnumeration(typedValue)) { XmlSchemaException e = new XmlSchemaException(Res.Sch_EnumerationValue, typedValue.ToString(), baseUri, lineNo, linePos); if (eventhandler != null) { eventhandler(sender, new ValidationEventArgs(e)); } else { throw e; } } } attdef.DefaultValueTyped = typedValue; } #if DEBUG catch (XmlSchemaException ex) { Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message); #else catch { #endif XmlSchemaException e = new XmlSchemaException(Res.Sch_AttributeDefaultDataType, attdef.Name.ToString(), baseUri, lineNo, linePos); if (eventhandler != null) { eventhandler(sender, new ValidationEventArgs(e)); } else { throw e; } } } internal void AddID(string name, object node) { // Note: It used to be true that we only called this if _fValidate was true, // but due to the fact that you can now dynamically type somethign as an ID // that is no longer true. if (IDs == null) { IDs = new Hashtable(); } IDs.Add(name, node); } public override object FindId(string name) { return IDs == null ? null : IDs[name]; } private void Push(XmlQualifiedName elementName) { context = (ValidationState)validationStack.Push(); if (context == null) { context = new ValidationState(); validationStack.AddToTop(context); } context.LocalName = elementName.Name; context.Namespace = elementName.Namespace; context.HasMatched = false; context.IsNill = false; context.NeedValidateChildren = false; } private void Pop() { if (validationStack.Length > 1) { validationStack.Pop(); context = (ValidationState)validationStack.Peek(); } } private void CheckForwardRefs() { IdRefNode next = idRefListHead; while (next != null) { if(FindId(next.Id) == null) { SendValidationEvent(new XmlSchemaException(Res.Sch_UndeclaredId, next.Id, reader.BaseURI, next.LineNo, next.LinePos)); } IdRefNode ptr = next.Next; next.Next = null; // unhook each object so it is cleaned up by Garbage Collector next = ptr; } // not needed any more. idRefListHead = null; } private XmlQualifiedName QualifiedName(string name, string ns) { return new XmlQualifiedName(name, XmlSchemaDatatype.XdrCanonizeUri(ns, NameTable, SchemaNames)); } }; #pragma warning restore 618 }