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