1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 namespace System.Xml.Schema 6 { 7 using System.IO; 8 using System.Text; 9 using System.Collections; 10 using System.Diagnostics; 11 using System.Globalization; 12 using System.Runtime.Versioning; 13 14 #pragma warning disable 618 15 internal sealed class XdrValidator : BaseValidator 16 { 17 private const int STACK_INCREMENT = 10; 18 private HWStack _validationStack; // validaton contexts 19 private Hashtable _attPresence; 20 private XmlQualifiedName _name = XmlQualifiedName.Empty; 21 private XmlNamespaceManager _nsManager; 22 private bool _isProcessContents = false; 23 private Hashtable _IDs; 24 private IdRefNode _idRefListHead; 25 private Parser _inlineSchemaParser = null; 26 private const string x_schema = "x-schema:"; 27 XdrValidator(BaseValidator validator)28 internal XdrValidator(BaseValidator validator) : base(validator) 29 { 30 Init(); 31 } 32 XdrValidator(XmlValidatingReaderImpl reader, XmlSchemaCollection schemaCollection, IValidationEventHandling eventHandling)33 internal XdrValidator(XmlValidatingReaderImpl reader, XmlSchemaCollection schemaCollection, IValidationEventHandling eventHandling) : base(reader, schemaCollection, eventHandling) 34 { 35 Init(); 36 } 37 Init()38 private void Init() 39 { 40 _nsManager = reader.NamespaceManager; 41 if (_nsManager == null) 42 { 43 _nsManager = new XmlNamespaceManager(NameTable); 44 _isProcessContents = true; 45 } 46 _validationStack = new HWStack(STACK_INCREMENT); 47 textValue = new StringBuilder(); 48 _name = XmlQualifiedName.Empty; 49 _attPresence = new Hashtable(); 50 Push(XmlQualifiedName.Empty); 51 schemaInfo = new SchemaInfo(); 52 checkDatatype = false; 53 } 54 Validate()55 public override void Validate() 56 { 57 if (IsInlineSchemaStarted) 58 { 59 ProcessInlineSchema(); 60 } 61 else 62 { 63 switch (reader.NodeType) 64 { 65 case XmlNodeType.Element: 66 ValidateElement(); 67 if (reader.IsEmptyElement) 68 { 69 goto case XmlNodeType.EndElement; 70 } 71 break; 72 case XmlNodeType.Whitespace: 73 ValidateWhitespace(); 74 break; 75 case XmlNodeType.Text: // text inside a node 76 case XmlNodeType.CDATA: // <![CDATA[...]]> 77 case XmlNodeType.SignificantWhitespace: 78 ValidateText(); 79 break; 80 case XmlNodeType.EndElement: 81 ValidateEndElement(); 82 break; 83 } 84 } 85 } 86 ValidateElement()87 private void ValidateElement() 88 { 89 elementName.Init(reader.LocalName, XmlSchemaDatatype.XdrCanonizeUri(reader.NamespaceURI, NameTable, SchemaNames)); 90 ValidateChildElement(); 91 if (SchemaNames.IsXDRRoot(elementName.Name, elementName.Namespace) && reader.Depth > 0) 92 { 93 _inlineSchemaParser = new Parser(SchemaType.XDR, NameTable, SchemaNames, EventHandler); 94 _inlineSchemaParser.StartParsing(reader, null); 95 _inlineSchemaParser.ParseReaderNode(); 96 } 97 else 98 { 99 ProcessElement(); 100 } 101 } 102 ValidateChildElement()103 private void ValidateChildElement() 104 { 105 if (context.NeedValidateChildren) 106 { 107 int errorCode = 0; 108 context.ElementDecl.ContentValidator.ValidateElement(elementName, context, out errorCode); 109 if (errorCode < 0) 110 { 111 XmlSchemaValidator.ElementValidationError(elementName, context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null); 112 } 113 } 114 } 115 116 private bool IsInlineSchemaStarted 117 { 118 get { return _inlineSchemaParser != null; } 119 } 120 ProcessInlineSchema()121 private void ProcessInlineSchema() 122 { 123 if (!_inlineSchemaParser.ParseReaderNode()) 124 { // Done 125 _inlineSchemaParser.FinishParsing(); 126 SchemaInfo xdrSchema = _inlineSchemaParser.XdrSchema; 127 if (xdrSchema != null && xdrSchema.ErrorCount == 0) 128 { 129 foreach (string inlineNS in xdrSchema.TargetNamespaces.Keys) 130 { 131 if (!this.schemaInfo.HasSchema(inlineNS)) 132 { 133 schemaInfo.Add(xdrSchema, EventHandler); 134 SchemaCollection.Add(inlineNS, xdrSchema, null, false); 135 break; 136 } 137 } 138 } 139 _inlineSchemaParser = null; 140 } 141 } 142 ProcessElement()143 private void ProcessElement() 144 { 145 Push(elementName); 146 if (_isProcessContents) 147 { 148 _nsManager.PopScope(); 149 } 150 context.ElementDecl = ThoroughGetElementDecl(); 151 if (context.ElementDecl != null) 152 { 153 ValidateStartElement(); 154 ValidateEndStartElement(); 155 context.NeedValidateChildren = true; 156 context.ElementDecl.ContentValidator.InitValidation(context); 157 } 158 } 159 ValidateEndElement()160 private void ValidateEndElement() 161 { 162 if (_isProcessContents) 163 { 164 _nsManager.PopScope(); 165 } 166 if (context.ElementDecl != null) 167 { 168 if (context.NeedValidateChildren) 169 { 170 if (!context.ElementDecl.ContentValidator.CompleteValidation(context)) 171 { 172 XmlSchemaValidator.CompleteValidationError(context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null); 173 } 174 } 175 if (checkDatatype) 176 { 177 string stringValue = !hasSibling ? textString : textValue.ToString(); // only for identity-constraint exception reporting 178 CheckValue(stringValue, null); 179 checkDatatype = false; 180 textValue.Length = 0; // cleanup 181 textString = string.Empty; 182 } 183 } 184 Pop(); 185 } 186 187 // SxS: This method processes resource names read from the source document and does not expose 188 // any resources to the caller. It is fine to suppress the SxS warning. ThoroughGetElementDecl()189 private SchemaElementDecl ThoroughGetElementDecl() 190 { 191 if (reader.Depth == 0) 192 { 193 LoadSchema(string.Empty); 194 } 195 if (reader.MoveToFirstAttribute()) 196 { 197 do 198 { 199 string objectNs = reader.NamespaceURI; 200 string objectName = reader.LocalName; 201 if (Ref.Equal(objectNs, SchemaNames.NsXmlNs)) 202 { 203 LoadSchema(reader.Value); 204 if (_isProcessContents) 205 { 206 _nsManager.AddNamespace(reader.Prefix.Length == 0 ? string.Empty : reader.LocalName, reader.Value); 207 } 208 } 209 if ( 210 Ref.Equal(objectNs, SchemaNames.QnDtDt.Namespace) && 211 Ref.Equal(objectName, SchemaNames.QnDtDt.Name) 212 ) 213 { 214 reader.SchemaTypeObject = XmlSchemaDatatype.FromXdrName(reader.Value); 215 } 216 } while (reader.MoveToNextAttribute()); 217 reader.MoveToElement(); 218 } 219 SchemaElementDecl elementDecl = schemaInfo.GetElementDecl(elementName); 220 if (elementDecl == null) 221 { 222 if (schemaInfo.TargetNamespaces.ContainsKey(context.Namespace)) 223 { 224 SendValidationEvent(SR.Sch_UndeclaredElement, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); 225 } 226 } 227 return elementDecl; 228 } 229 ValidateStartElement()230 private void ValidateStartElement() 231 { 232 if (context.ElementDecl != null) 233 { 234 if (context.ElementDecl.SchemaType != null) 235 { 236 reader.SchemaTypeObject = context.ElementDecl.SchemaType; 237 } 238 else 239 { 240 reader.SchemaTypeObject = context.ElementDecl.Datatype; 241 } 242 if (reader.IsEmptyElement && !context.IsNill && context.ElementDecl.DefaultValueTyped != null) 243 { 244 reader.TypedValueObject = context.ElementDecl.DefaultValueTyped; 245 context.IsNill = true; // reusing IsNill 246 } 247 if (this.context.ElementDecl.HasRequiredAttribute) 248 { 249 _attPresence.Clear(); 250 } 251 } 252 253 if (reader.MoveToFirstAttribute()) 254 { 255 do 256 { 257 if ((object)reader.NamespaceURI == (object)SchemaNames.NsXmlNs) 258 { 259 continue; 260 } 261 262 try 263 { 264 reader.SchemaTypeObject = null; 265 SchemaAttDef attnDef = schemaInfo.GetAttributeXdr(context.ElementDecl, QualifiedName(reader.LocalName, reader.NamespaceURI)); 266 if (attnDef != null) 267 { 268 if (context.ElementDecl != null && context.ElementDecl.HasRequiredAttribute) 269 { 270 _attPresence.Add(attnDef.Name, attnDef); 271 } 272 reader.SchemaTypeObject = (attnDef.SchemaType != null) ? (object)attnDef.SchemaType : (object)attnDef.Datatype; 273 if (attnDef.Datatype != null) 274 { 275 string attributeValue = reader.Value; 276 // need to check the contents of this attribute to make sure 277 // it is valid according to the specified attribute type. 278 CheckValue(attributeValue, attnDef); 279 } 280 } 281 } 282 catch (XmlSchemaException e) 283 { 284 e.SetSource(reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); 285 SendValidationEvent(e); 286 } 287 } while (reader.MoveToNextAttribute()); 288 reader.MoveToElement(); 289 } 290 } 291 ValidateEndStartElement()292 private void ValidateEndStartElement() 293 { 294 if (context.ElementDecl.HasDefaultAttribute) 295 { 296 for (int i = 0; i < context.ElementDecl.DefaultAttDefs.Count; ++i) 297 { 298 reader.AddDefaultAttribute((SchemaAttDef)context.ElementDecl.DefaultAttDefs[i]); 299 } 300 } 301 if (context.ElementDecl.HasRequiredAttribute) 302 { 303 try 304 { 305 context.ElementDecl.CheckAttributes(_attPresence, reader.StandAlone); 306 } 307 catch (XmlSchemaException e) 308 { 309 e.SetSource(reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); 310 SendValidationEvent(e); 311 } 312 } 313 if (context.ElementDecl.Datatype != null) 314 { 315 checkDatatype = true; 316 hasSibling = false; 317 textString = string.Empty; 318 textValue.Length = 0; 319 } 320 } 321 LoadSchemaFromLocation(string uri)322 private void LoadSchemaFromLocation(string uri) 323 { 324 // is x-schema 325 if (!XdrBuilder.IsXdrSchema(uri)) 326 { 327 return; 328 } 329 string url = uri.Substring(x_schema.Length); 330 XmlReader reader = null; 331 SchemaInfo xdrSchema = null; 332 try 333 { 334 Uri ruri = this.XmlResolver.ResolveUri(BaseUri, url); 335 Stream stm = (Stream)this.XmlResolver.GetEntity(ruri, null, null); 336 reader = new XmlTextReader(ruri.ToString(), stm, NameTable); 337 ((XmlTextReader)reader).XmlResolver = this.XmlResolver; 338 Parser parser = new Parser(SchemaType.XDR, NameTable, SchemaNames, EventHandler); 339 parser.XmlResolver = this.XmlResolver; 340 parser.Parse(reader, uri); 341 while (reader.Read()) ;// wellformness check 342 xdrSchema = parser.XdrSchema; 343 } 344 catch (XmlSchemaException e) 345 { 346 SendValidationEvent(SR.Sch_CannotLoadSchema, new string[] { uri, e.Message }, XmlSeverityType.Error); 347 } 348 catch (Exception e) 349 { 350 SendValidationEvent(SR.Sch_CannotLoadSchema, new string[] { uri, e.Message }, XmlSeverityType.Warning); 351 } 352 finally 353 { 354 if (reader != null) 355 { 356 reader.Close(); 357 } 358 } 359 if (xdrSchema != null && xdrSchema.ErrorCount == 0) 360 { 361 schemaInfo.Add(xdrSchema, EventHandler); 362 SchemaCollection.Add(uri, xdrSchema, null, false); 363 } 364 } 365 LoadSchema(string uri)366 private void LoadSchema(string uri) 367 { 368 if (this.schemaInfo.TargetNamespaces.ContainsKey(uri)) 369 { 370 return; 371 } 372 if (this.XmlResolver == null) 373 { 374 return; 375 } 376 377 SchemaInfo schemaInfo = null; 378 if (SchemaCollection != null) 379 schemaInfo = SchemaCollection.GetSchemaInfo(uri); 380 if (schemaInfo != null) 381 { 382 if (schemaInfo.SchemaType != SchemaType.XDR) 383 { 384 throw new XmlException(SR.Xml_MultipleValidaitonTypes, string.Empty, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition); 385 } 386 this.schemaInfo.Add(schemaInfo, EventHandler); 387 return; 388 } 389 LoadSchemaFromLocation(uri); 390 } 391 392 private bool HasSchema { get { return schemaInfo.SchemaType != SchemaType.None; } } 393 394 public override bool PreserveWhitespace 395 { 396 get { return context.ElementDecl != null ? context.ElementDecl.ContentValidator.PreserveWhitespace : false; } 397 } 398 ProcessTokenizedType( XmlTokenizedType ttype, string name )399 private void ProcessTokenizedType( 400 XmlTokenizedType ttype, 401 string name 402 ) 403 { 404 switch (ttype) 405 { 406 case XmlTokenizedType.ID: 407 if (FindId(name) != null) 408 { 409 SendValidationEvent(SR.Sch_DupId, name); 410 } 411 else 412 { 413 AddID(name, context.LocalName); 414 } 415 break; 416 case XmlTokenizedType.IDREF: 417 object p = FindId(name); 418 if (p == null) 419 { // add it to linked list to check it later 420 _idRefListHead = new IdRefNode(_idRefListHead, name, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition); 421 } 422 break; 423 case XmlTokenizedType.ENTITY: 424 ProcessEntity(schemaInfo, name, this, EventHandler, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); 425 break; 426 default: 427 break; 428 } 429 } 430 431 CompleteValidation()432 public override void CompleteValidation() 433 { 434 if (HasSchema) 435 { 436 CheckForwardRefs(); 437 } 438 else 439 { 440 SendValidationEvent(new XmlSchemaException(SR.Xml_NoValidation, string.Empty), XmlSeverityType.Warning); 441 } 442 } 443 444 CheckValue( string value, SchemaAttDef attdef )445 private void CheckValue( 446 string value, 447 SchemaAttDef attdef 448 ) 449 { 450 try 451 { 452 reader.TypedValueObject = null; 453 bool isAttn = attdef != null; 454 XmlSchemaDatatype dtype = isAttn ? attdef.Datatype : context.ElementDecl.Datatype; 455 if (dtype == null) 456 { 457 return; // no reason to check 458 } 459 460 if (dtype.TokenizedType != XmlTokenizedType.CDATA) 461 { 462 value = value.Trim(); 463 } 464 if (value.Length == 0) 465 { 466 return; // don't need to check 467 } 468 469 470 object typedValue = dtype.ParseValue(value, NameTable, _nsManager); 471 reader.TypedValueObject = typedValue; 472 // Check special types 473 XmlTokenizedType ttype = dtype.TokenizedType; 474 if (ttype == XmlTokenizedType.ENTITY || ttype == XmlTokenizedType.ID || ttype == XmlTokenizedType.IDREF) 475 { 476 if (dtype.Variety == XmlSchemaDatatypeVariety.List) 477 { 478 string[] ss = (string[])typedValue; 479 for (int i = 0; i < ss.Length; ++i) 480 { 481 ProcessTokenizedType(dtype.TokenizedType, ss[i]); 482 } 483 } 484 else 485 { 486 ProcessTokenizedType(dtype.TokenizedType, (string)typedValue); 487 } 488 } 489 490 SchemaDeclBase decl = isAttn ? (SchemaDeclBase)attdef : (SchemaDeclBase)context.ElementDecl; 491 492 if (decl.MaxLength != uint.MaxValue) 493 { 494 if (value.Length > decl.MaxLength) 495 { 496 SendValidationEvent(SR.Sch_MaxLengthConstraintFailed, value); 497 } 498 } 499 if (decl.MinLength != uint.MaxValue) 500 { 501 if (value.Length < decl.MinLength) 502 { 503 SendValidationEvent(SR.Sch_MinLengthConstraintFailed, value); 504 } 505 } 506 if (decl.Values != null && !decl.CheckEnumeration(typedValue)) 507 { 508 if (dtype.TokenizedType == XmlTokenizedType.NOTATION) 509 { 510 SendValidationEvent(SR.Sch_NotationValue, typedValue.ToString()); 511 } 512 else 513 { 514 SendValidationEvent(SR.Sch_EnumerationValue, typedValue.ToString()); 515 } 516 } 517 if (!decl.CheckValue(typedValue)) 518 { 519 if (isAttn) 520 { 521 SendValidationEvent(SR.Sch_FixedAttributeValue, attdef.Name.ToString()); 522 } 523 else 524 { 525 SendValidationEvent(SR.Sch_FixedElementValue, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); 526 } 527 } 528 } 529 catch (XmlSchemaException) 530 { 531 if (attdef != null) 532 { 533 SendValidationEvent(SR.Sch_AttributeValueDataType, attdef.Name.ToString()); 534 } 535 else 536 { 537 SendValidationEvent(SR.Sch_ElementValueDataType, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); 538 } 539 } 540 } 541 CheckDefaultValue( string value, SchemaAttDef attdef, SchemaInfo sinfo, XmlNamespaceManager nsManager, XmlNameTable NameTable, object sender, ValidationEventHandler eventhandler, string baseUri, int lineNo, int linePos )542 public static void CheckDefaultValue( 543 string value, 544 SchemaAttDef attdef, 545 SchemaInfo sinfo, 546 XmlNamespaceManager nsManager, 547 XmlNameTable NameTable, 548 object sender, 549 ValidationEventHandler eventhandler, 550 string baseUri, 551 int lineNo, 552 int linePos 553 ) 554 { 555 try 556 { 557 XmlSchemaDatatype dtype = attdef.Datatype; 558 if (dtype == null) 559 { 560 return; // no reason to check 561 } 562 563 if (dtype.TokenizedType != XmlTokenizedType.CDATA) 564 { 565 value = value.Trim(); 566 } 567 if (value.Length == 0) 568 { 569 return; // don't need to check 570 } 571 object typedValue = dtype.ParseValue(value, NameTable, nsManager); 572 573 // Check special types 574 XmlTokenizedType ttype = dtype.TokenizedType; 575 if (ttype == XmlTokenizedType.ENTITY) 576 { 577 if (dtype.Variety == XmlSchemaDatatypeVariety.List) 578 { 579 string[] ss = (string[])typedValue; 580 for (int i = 0; i < ss.Length; ++i) 581 { 582 ProcessEntity(sinfo, ss[i], sender, eventhandler, baseUri, lineNo, linePos); 583 } 584 } 585 else 586 { 587 ProcessEntity(sinfo, (string)typedValue, sender, eventhandler, baseUri, lineNo, linePos); 588 } 589 } 590 else if (ttype == XmlTokenizedType.ENUMERATION) 591 { 592 if (!attdef.CheckEnumeration(typedValue)) 593 { 594 XmlSchemaException e = new XmlSchemaException(SR.Sch_EnumerationValue, typedValue.ToString(), baseUri, lineNo, linePos); 595 if (eventhandler != null) 596 { 597 eventhandler(sender, new ValidationEventArgs(e)); 598 } 599 else 600 { 601 throw e; 602 } 603 } 604 } 605 attdef.DefaultValueTyped = typedValue; 606 } 607 #if DEBUG 608 catch (XmlSchemaException ex) 609 { 610 Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message); 611 #else 612 catch 613 { 614 #endif 615 XmlSchemaException e = new XmlSchemaException(SR.Sch_AttributeDefaultDataType, attdef.Name.ToString(), baseUri, lineNo, linePos); 616 if (eventhandler != null) 617 { 618 eventhandler(sender, new ValidationEventArgs(e)); 619 } 620 else 621 { 622 throw e; 623 } 624 } 625 } 626 627 internal void AddID(string name, object node) 628 { 629 // Note: It used to be true that we only called this if _fValidate was true, 630 // but due to the fact that you can now dynamically type somethign as an ID 631 // that is no longer true. 632 if (_IDs == null) 633 { 634 _IDs = new Hashtable(); 635 } 636 637 _IDs.Add(name, node); 638 } 639 640 public override object FindId(string name) 641 { 642 return _IDs == null ? null : _IDs[name]; 643 } 644 645 private void Push(XmlQualifiedName elementName) 646 { 647 context = (ValidationState)_validationStack.Push(); 648 if (context == null) 649 { 650 context = new ValidationState(); 651 _validationStack.AddToTop(context); 652 } 653 context.LocalName = elementName.Name; 654 context.Namespace = elementName.Namespace; 655 context.HasMatched = false; 656 context.IsNill = false; 657 context.NeedValidateChildren = false; 658 } 659 660 private void Pop() 661 { 662 if (_validationStack.Length > 1) 663 { 664 _validationStack.Pop(); 665 context = (ValidationState)_validationStack.Peek(); 666 } 667 } 668 669 private void CheckForwardRefs() 670 { 671 IdRefNode next = _idRefListHead; 672 while (next != null) 673 { 674 if (FindId(next.Id) == null) 675 { 676 SendValidationEvent(new XmlSchemaException(SR.Sch_UndeclaredId, next.Id, reader.BaseURI, next.LineNo, next.LinePos)); 677 } 678 IdRefNode ptr = next.Next; 679 next.Next = null; // unhook each object so it is cleaned up by Garbage Collector 680 next = ptr; 681 } 682 // not needed any more. 683 _idRefListHead = null; 684 } 685 686 private XmlQualifiedName QualifiedName(string name, string ns) 687 { 688 return new XmlQualifiedName(name, XmlSchemaDatatype.XdrCanonizeUri(ns, NameTable, SchemaNames)); 689 } 690 }; 691 #pragma warning restore 618 692 } 693 694