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