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