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; 8 using System.Collections; 9 using System.Text; 10 using System.IO; 11 using System.Net; 12 using System.Diagnostics; 13 using System.Xml.Schema; 14 using System.Xml.XPath; 15 16 #pragma warning disable 618 17 18 internal sealed class DtdValidator : BaseValidator 19 { 20 //required by ParseValue 21 private class NamespaceManager : XmlNamespaceManager 22 { LookupNamespace(string prefix)23 public override string LookupNamespace(string prefix) { return prefix; } 24 } 25 26 private static NamespaceManager s_namespaceManager = new NamespaceManager(); 27 private const int STACK_INCREMENT = 10; 28 private HWStack _validationStack; // validaton contexts 29 private Hashtable _attPresence; 30 private XmlQualifiedName _name = XmlQualifiedName.Empty; 31 private Hashtable _IDs; 32 private IdRefNode _idRefListHead; 33 private bool _processIdentityConstraints; 34 DtdValidator(XmlValidatingReaderImpl reader, IValidationEventHandling eventHandling, bool processIdentityConstraints)35 internal DtdValidator(XmlValidatingReaderImpl reader, IValidationEventHandling eventHandling, bool processIdentityConstraints) : base(reader, null, eventHandling) 36 { 37 _processIdentityConstraints = processIdentityConstraints; 38 Init(); 39 } 40 Init()41 private void Init() 42 { 43 Debug.Assert(reader != null); 44 _validationStack = new HWStack(STACK_INCREMENT); 45 textValue = new StringBuilder(); 46 _name = XmlQualifiedName.Empty; 47 _attPresence = new Hashtable(); 48 schemaInfo = new SchemaInfo(); 49 checkDatatype = false; 50 Push(_name); 51 } 52 Validate()53 public override void Validate() 54 { 55 if (schemaInfo.SchemaType == SchemaType.DTD) 56 { 57 switch (reader.NodeType) 58 { 59 case XmlNodeType.Element: 60 ValidateElement(); 61 if (reader.IsEmptyElement) 62 { 63 goto case XmlNodeType.EndElement; 64 } 65 break; 66 case XmlNodeType.Whitespace: 67 case XmlNodeType.SignificantWhitespace: 68 if (MeetsStandAloneConstraint()) 69 { 70 ValidateWhitespace(); 71 } 72 break; 73 case XmlNodeType.ProcessingInstruction: 74 case XmlNodeType.Comment: 75 ValidatePIComment(); 76 break; 77 78 case XmlNodeType.Text: // text inside a node 79 case XmlNodeType.CDATA: // <![CDATA[...]]> 80 ValidateText(); 81 break; 82 case XmlNodeType.EntityReference: 83 if (!GenEntity(new XmlQualifiedName(reader.LocalName, reader.Prefix))) 84 { 85 ValidateText(); 86 } 87 break; 88 case XmlNodeType.EndElement: 89 ValidateEndElement(); 90 break; 91 } 92 } 93 else 94 { 95 if (reader.Depth == 0 && 96 reader.NodeType == XmlNodeType.Element) 97 { 98 SendValidationEvent(SR.Xml_NoDTDPresent, _name.ToString(), XmlSeverityType.Warning); 99 } 100 } 101 } 102 MeetsStandAloneConstraint()103 private bool MeetsStandAloneConstraint() 104 { 105 if (reader.StandAlone && // VC 1 - iv 106 context.ElementDecl != null && 107 context.ElementDecl.IsDeclaredInExternal && 108 context.ElementDecl.ContentValidator.ContentType == XmlSchemaContentType.ElementOnly) 109 { 110 SendValidationEvent(SR.Sch_StandAlone); 111 return false; 112 } 113 return true; 114 } 115 ValidatePIComment()116 private void ValidatePIComment() 117 { 118 // When validating with a dtd, empty elements should be lexically empty. 119 if (context.NeedValidateChildren) 120 { 121 if (context.ElementDecl.ContentValidator == ContentValidator.Empty) 122 { 123 SendValidationEvent(SR.Sch_InvalidPIComment); 124 } 125 } 126 } 127 ValidateElement()128 private void ValidateElement() 129 { 130 elementName.Init(reader.LocalName, reader.Prefix); 131 if ((reader.Depth == 0) && 132 (!schemaInfo.DocTypeName.IsEmpty) && 133 (!schemaInfo.DocTypeName.Equals(elementName))) 134 { //VC 1 135 SendValidationEvent(SR.Sch_RootMatchDocType); 136 } 137 else 138 { 139 ValidateChildElement(); 140 } 141 ProcessElement(); 142 } 143 ValidateChildElement()144 private void ValidateChildElement() 145 { 146 Debug.Assert(reader.NodeType == XmlNodeType.Element); 147 if (context.NeedValidateChildren) 148 { //i think i can get away with removing this if cond since won't make this call for documentelement 149 int errorCode = 0; 150 context.ElementDecl.ContentValidator.ValidateElement(elementName, context, out errorCode); 151 if (errorCode < 0) 152 { 153 XmlSchemaValidator.ElementValidationError(elementName, context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null); 154 } 155 } 156 } 157 ValidateStartElement()158 private void ValidateStartElement() 159 { 160 if (context.ElementDecl != null) 161 { 162 Reader.SchemaTypeObject = context.ElementDecl.SchemaType; 163 164 if (Reader.IsEmptyElement && context.ElementDecl.DefaultValueTyped != null) 165 { 166 Reader.TypedValueObject = context.ElementDecl.DefaultValueTyped; 167 context.IsNill = true; // reusing IsNill - what is this flag later used for?? 168 } 169 if (context.ElementDecl.HasRequiredAttribute) 170 { 171 _attPresence.Clear(); 172 } 173 } 174 175 if (Reader.MoveToFirstAttribute()) 176 { 177 do 178 { 179 try 180 { 181 reader.SchemaTypeObject = null; 182 SchemaAttDef attnDef = context.ElementDecl.GetAttDef(new XmlQualifiedName(reader.LocalName, reader.Prefix)); 183 if (attnDef != null) 184 { 185 if (context.ElementDecl != null && context.ElementDecl.HasRequiredAttribute) 186 { 187 _attPresence.Add(attnDef.Name, attnDef); 188 } 189 Reader.SchemaTypeObject = attnDef.SchemaType; 190 191 if (attnDef.Datatype != null && !reader.IsDefault) 192 { //Since XmlTextReader adds default attributes, do not check again 193 // set typed value 194 CheckValue(Reader.Value, attnDef); 195 } 196 } 197 else 198 { 199 SendValidationEvent(SR.Sch_UndeclaredAttribute, reader.Name); 200 } 201 } 202 catch (XmlSchemaException e) 203 { 204 e.SetSource(Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); 205 SendValidationEvent(e); 206 } 207 } while (Reader.MoveToNextAttribute()); 208 Reader.MoveToElement(); 209 } 210 } 211 ValidateEndStartElement()212 private void ValidateEndStartElement() 213 { 214 if (context.ElementDecl.HasRequiredAttribute) 215 { 216 try 217 { 218 context.ElementDecl.CheckAttributes(_attPresence, Reader.StandAlone); 219 } 220 catch (XmlSchemaException e) 221 { 222 e.SetSource(Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); 223 SendValidationEvent(e); 224 } 225 } 226 227 if (context.ElementDecl.Datatype != null) 228 { 229 checkDatatype = true; 230 hasSibling = false; 231 textString = string.Empty; 232 textValue.Length = 0; 233 } 234 } 235 ProcessElement()236 private void ProcessElement() 237 { 238 SchemaElementDecl elementDecl = schemaInfo.GetElementDecl(elementName); 239 Push(elementName); 240 if (elementDecl != null) 241 { 242 context.ElementDecl = elementDecl; 243 ValidateStartElement(); 244 ValidateEndStartElement(); 245 context.NeedValidateChildren = true; 246 elementDecl.ContentValidator.InitValidation(context); 247 } 248 else 249 { 250 SendValidationEvent(SR.Sch_UndeclaredElement, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); 251 context.ElementDecl = null; 252 } 253 } 254 CompleteValidation()255 public override void CompleteValidation() 256 { 257 if (schemaInfo.SchemaType == SchemaType.DTD) 258 { 259 do 260 { 261 ValidateEndElement(); 262 } while (Pop()); 263 CheckForwardRefs(); 264 } 265 } 266 ValidateEndElement()267 private void ValidateEndElement() 268 { 269 if (context.ElementDecl != null) 270 { 271 if (context.NeedValidateChildren) 272 { 273 if (!context.ElementDecl.ContentValidator.CompleteValidation(context)) 274 { 275 XmlSchemaValidator.CompleteValidationError(context, EventHandler, reader, reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition, null); 276 } 277 } 278 279 if (checkDatatype) 280 { 281 string stringValue = !hasSibling ? textString : textValue.ToString(); // only for identity-constraint exception reporting 282 CheckValue(stringValue, null); 283 checkDatatype = false; 284 textValue.Length = 0; // cleanup 285 textString = string.Empty; 286 } 287 } 288 Pop(); 289 } 290 291 public override bool PreserveWhitespace 292 { 293 get { return context.ElementDecl != null ? context.ElementDecl.ContentValidator.PreserveWhitespace : false; } 294 } 295 296 ProcessTokenizedType( XmlTokenizedType ttype, string name )297 private void ProcessTokenizedType( 298 XmlTokenizedType ttype, 299 string name 300 ) 301 { 302 switch (ttype) 303 { 304 case XmlTokenizedType.ID: 305 if (_processIdentityConstraints) 306 { 307 if (FindId(name) != null) 308 { 309 SendValidationEvent(SR.Sch_DupId, name); 310 } 311 else 312 { 313 AddID(name, context.LocalName); 314 } 315 } 316 break; 317 case XmlTokenizedType.IDREF: 318 if (_processIdentityConstraints) 319 { 320 object p = FindId(name); 321 if (p == null) 322 { // add it to linked list to check it later 323 _idRefListHead = new IdRefNode(_idRefListHead, name, this.PositionInfo.LineNumber, this.PositionInfo.LinePosition); 324 } 325 } 326 327 break; 328 case XmlTokenizedType.ENTITY: 329 ProcessEntity(schemaInfo, name, this, EventHandler, Reader.BaseURI, PositionInfo.LineNumber, PositionInfo.LinePosition); 330 break; 331 default: 332 break; 333 } 334 } 335 336 //check the contents of this attribute to ensure it is valid according to the specified attribute type. CheckValue(string value, SchemaAttDef attdef)337 private void CheckValue(string value, SchemaAttDef attdef) 338 { 339 try 340 { 341 reader.TypedValueObject = null; 342 bool isAttn = attdef != null; 343 XmlSchemaDatatype dtype = isAttn ? attdef.Datatype : context.ElementDecl.Datatype; 344 if (dtype == null) 345 { 346 return; // no reason to check 347 } 348 349 if (dtype.TokenizedType != XmlTokenizedType.CDATA) 350 { 351 value = value.Trim(); 352 } 353 354 object typedValue = dtype.ParseValue(value, NameTable, s_namespaceManager); 355 reader.TypedValueObject = typedValue; 356 // Check special types 357 XmlTokenizedType ttype = dtype.TokenizedType; 358 if (ttype == XmlTokenizedType.ENTITY || ttype == XmlTokenizedType.ID || ttype == XmlTokenizedType.IDREF) 359 { 360 if (dtype.Variety == XmlSchemaDatatypeVariety.List) 361 { 362 string[] ss = (string[])typedValue; 363 for (int i = 0; i < ss.Length; ++i) 364 { 365 ProcessTokenizedType(dtype.TokenizedType, ss[i]); 366 } 367 } 368 else 369 { 370 ProcessTokenizedType(dtype.TokenizedType, (string)typedValue); 371 } 372 } 373 374 SchemaDeclBase decl = isAttn ? (SchemaDeclBase)attdef : (SchemaDeclBase)context.ElementDecl; 375 if (decl.Values != null && !decl.CheckEnumeration(typedValue)) 376 { 377 if (dtype.TokenizedType == XmlTokenizedType.NOTATION) 378 { 379 SendValidationEvent(SR.Sch_NotationValue, typedValue.ToString()); 380 } 381 else 382 { 383 SendValidationEvent(SR.Sch_EnumerationValue, typedValue.ToString()); 384 } 385 } 386 if (!decl.CheckValue(typedValue)) 387 { 388 if (isAttn) 389 { 390 SendValidationEvent(SR.Sch_FixedAttributeValue, attdef.Name.ToString()); 391 } 392 else 393 { 394 SendValidationEvent(SR.Sch_FixedElementValue, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); 395 } 396 } 397 } 398 catch (XmlSchemaException) 399 { 400 if (attdef != null) 401 { 402 SendValidationEvent(SR.Sch_AttributeValueDataType, attdef.Name.ToString()); 403 } 404 else 405 { 406 SendValidationEvent(SR.Sch_ElementValueDataType, XmlSchemaValidator.QNameString(context.LocalName, context.Namespace)); 407 } 408 } 409 } 410 411 AddID(string name, object node)412 internal void AddID(string name, object node) 413 { 414 // Note: It used to be true that we only called this if _fValidate was true, 415 // but due to the fact that you can now dynamically type somethign as an ID 416 // that is no longer true. 417 if (_IDs == null) 418 { 419 _IDs = new Hashtable(); 420 } 421 422 _IDs.Add(name, node); 423 } 424 FindId(string name)425 public override object FindId(string name) 426 { 427 return _IDs == null ? null : _IDs[name]; 428 } 429 GenEntity(XmlQualifiedName qname)430 private bool GenEntity(XmlQualifiedName qname) 431 { 432 string n = qname.Name; 433 if (n[0] == '#') 434 { // char entity reference 435 return false; 436 } 437 else if (SchemaEntity.IsPredefinedEntity(n)) 438 { 439 return false; 440 } 441 else 442 { 443 SchemaEntity en = GetEntity(qname, false); 444 if (en == null) 445 { 446 // well-formness error, see xml spec [68] 447 throw new XmlException(SR.Xml_UndeclaredEntity, n); 448 } 449 if (!en.NData.IsEmpty) 450 { 451 // well-formness error, see xml spec [68] 452 throw new XmlException(SR.Xml_UnparsedEntityRef, n); 453 } 454 455 if (reader.StandAlone && en.DeclaredInExternal) 456 { 457 SendValidationEvent(SR.Sch_StandAlone); 458 } 459 return true; 460 } 461 } 462 463 GetEntity(XmlQualifiedName qname, bool fParameterEntity)464 private SchemaEntity GetEntity(XmlQualifiedName qname, bool fParameterEntity) 465 { 466 SchemaEntity entity; 467 if (fParameterEntity) 468 { 469 if (schemaInfo.ParameterEntities.TryGetValue(qname, out entity)) 470 { 471 return entity; 472 } 473 } 474 else 475 { 476 if (schemaInfo.GeneralEntities.TryGetValue(qname, out entity)) 477 { 478 return entity; 479 } 480 } 481 return null; 482 } 483 CheckForwardRefs()484 private void CheckForwardRefs() 485 { 486 IdRefNode next = _idRefListHead; 487 while (next != null) 488 { 489 if (FindId(next.Id) == null) 490 { 491 SendValidationEvent(new XmlSchemaException(SR.Sch_UndeclaredId, next.Id, reader.BaseURI, next.LineNo, next.LinePos)); 492 } 493 IdRefNode ptr = next.Next; 494 next.Next = null; // unhook each object so it is cleaned up by Garbage Collector 495 next = ptr; 496 } 497 // not needed any more. 498 _idRefListHead = null; 499 } 500 Push(XmlQualifiedName elementName)501 private void Push(XmlQualifiedName elementName) 502 { 503 context = (ValidationState)_validationStack.Push(); 504 if (context == null) 505 { 506 context = new ValidationState(); 507 _validationStack.AddToTop(context); 508 } 509 context.LocalName = elementName.Name; 510 context.Namespace = elementName.Namespace; 511 context.HasMatched = false; 512 context.IsNill = false; 513 context.NeedValidateChildren = false; 514 } 515 Pop()516 private bool Pop() 517 { 518 if (_validationStack.Length > 1) 519 { 520 _validationStack.Pop(); 521 context = (ValidationState)_validationStack.Peek(); 522 return true; 523 } 524 return false; 525 } 526 SetDefaultTypedValue( SchemaAttDef attdef, IDtdParserAdapter readerAdapter )527 public static void SetDefaultTypedValue( 528 SchemaAttDef attdef, 529 IDtdParserAdapter readerAdapter 530 ) 531 { 532 try 533 { 534 string value = attdef.DefaultValueExpanded; 535 XmlSchemaDatatype dtype = attdef.Datatype; 536 if (dtype == null) 537 { 538 return; // no reason to check 539 } 540 if (dtype.TokenizedType != XmlTokenizedType.CDATA) 541 { 542 value = value.Trim(); 543 } 544 attdef.DefaultValueTyped = dtype.ParseValue(value, readerAdapter.NameTable, readerAdapter.NamespaceResolver); 545 } 546 #if DEBUG 547 catch (XmlSchemaException ex) 548 { 549 Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message); 550 #else 551 catch (Exception) 552 { 553 #endif 554 IValidationEventHandling eventHandling = ((IDtdParserAdapterWithValidation)readerAdapter).ValidationEventHandling; 555 if (eventHandling != null) 556 { 557 XmlSchemaException e = new XmlSchemaException(SR.Sch_AttributeDefaultDataType, attdef.Name.ToString()); 558 eventHandling.SendEvent(e, XmlSeverityType.Error); 559 } 560 } 561 } 562 563 public static void CheckDefaultValue( 564 SchemaAttDef attdef, 565 SchemaInfo sinfo, 566 IValidationEventHandling eventHandling, 567 string baseUriStr 568 ) 569 { 570 try 571 { 572 if (baseUriStr == null) 573 { 574 baseUriStr = string.Empty; 575 } 576 XmlSchemaDatatype dtype = attdef.Datatype; 577 if (dtype == null) 578 { 579 return; // no reason to check 580 } 581 object typedValue = attdef.DefaultValueTyped; 582 583 // Check special types 584 XmlTokenizedType ttype = dtype.TokenizedType; 585 if (ttype == XmlTokenizedType.ENTITY) 586 { 587 if (dtype.Variety == XmlSchemaDatatypeVariety.List) 588 { 589 string[] ss = (string[])typedValue; 590 for (int i = 0; i < ss.Length; ++i) 591 { 592 ProcessEntity(sinfo, ss[i], eventHandling, baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition); 593 } 594 } 595 else 596 { 597 ProcessEntity(sinfo, (string)typedValue, eventHandling, baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition); 598 } 599 } 600 else if (ttype == XmlTokenizedType.ENUMERATION) 601 { 602 if (!attdef.CheckEnumeration(typedValue)) 603 { 604 if (eventHandling != null) 605 { 606 XmlSchemaException e = new XmlSchemaException(SR.Sch_EnumerationValue, typedValue.ToString(), baseUriStr, attdef.ValueLineNumber, attdef.ValueLinePosition); 607 eventHandling.SendEvent(e, XmlSeverityType.Error); 608 } 609 } 610 } 611 } 612 #if DEBUG 613 catch (XmlSchemaException ex) 614 { 615 Debug.WriteLineIf(DiagnosticsSwitches.XmlSchema.TraceError, ex.Message); 616 #else 617 catch (Exception) 618 { 619 #endif 620 621 if (eventHandling != null) 622 { 623 XmlSchemaException e = new XmlSchemaException(SR.Sch_AttributeDefaultDataType, attdef.Name.ToString()); 624 eventHandling.SendEvent(e, XmlSeverityType.Error); 625 } 626 } 627 } 628 } 629 #pragma warning restore 618 630 } 631 632