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