Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.Web / UI / WebControls / xml.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="xml.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.UI.WebControls {
8
9     using System;
10     using System.IO;
11     using System.Collections;
12     using System.Collections.Specialized;
13     using System.ComponentModel;
14     using System.ComponentModel.Design;
15     using System.Diagnostics.CodeAnalysis;
16     using System.Drawing.Design;
17     using System.Globalization;
18     using System.Web;
19     using System.Web.Util;
20     using System.Web.UI;
21     using System.Web.Caching;
22     using System.Web.Hosting;
23     using System.Security.Policy;
24     using System.Xml;
25     using System.Xml.Xsl;
26     using System.Xml.XPath;
27     using System.Security.Permissions;
28
29     public class XmlBuilder : ControlBuilder {
30
31
32         public override void AppendLiteralString(string s) {}
33
34
35         public override Type GetChildControlType(string tagName, IDictionary attribs) {
36             return null;
37         }
38
39
40         public override bool NeedsTagInnerText() { return true; }
41
42
43         [SuppressMessage("Microsoft.Security", "MSEC1220:ReviewDtdProcessingAssignment", Justification = "Dtd processing is needed for back-compat, but is being done as safely as possible.")]
44         [SuppressMessage("Microsoft.Security.Xml", "CA3069:ReviewDtdProcessingAssignment", Justification = "Dtd processing is needed for back-compat, but is being done as safely as possible.")]
45         public override void SetTagInnerText(string text) {
46             if (!Util.IsWhiteSpaceString(text)) {
47
48                 // Trim the initial whitespaces since XML is very picky (ASURT 58100)
49                 int iFirstNonWhiteSpace = Util.FirstNonWhiteSpaceIndex(text);
50                 string textNoWS = text.Substring(iFirstNonWhiteSpace);
51
52                 // Update the line number to point to the correct line in the xml
53                 // block (ASURT 58233).
54                 Line += Util.LineCount(text, 0, iFirstNonWhiteSpace);
55
56                 // Parse the XML here just to cause a parse error in case it is
57                 // malformed.  It will be parsed again at runtime.
58                 XmlDocument document = new XmlDocument();
59                 XmlReaderSettings readerSettings = XmlUtils.CreateXmlReaderSettings();
60                 readerSettings.LineNumberOffset = Line - 1;
61
62                 // VSWhidbey 546662: XmlReader has different default settings than XmlTextReader which was used in Everett
63                 readerSettings.DtdProcessing = DtdProcessing.Parse;
64                 readerSettings.CheckCharacters = false;
65                 
66                 XmlReader dataReader = XmlUtils.CreateXmlReader(new StringReader(textNoWS), string.Empty, readerSettings);
67
68                 try {
69                     document.Load(dataReader);
70                 }
71                 catch (XmlException e) {
72                     // Xml exception sometimes returns -1 for the line, in which case we ignore it.
73                     if (e.LineNumber >= 0) {
74                         Line = e.LineNumber;
75                     }
76
77                     throw;
78                 }
79
80                 base.AppendLiteralString(text);
81             }
82         }
83     }
84
85
86     /// <devdoc>
87     ///    <para>[To be supplied.]</para>
88     /// </devdoc>
89     [
90     DefaultProperty("DocumentSource"),
91     PersistChildren(false, true),
92     ControlBuilderAttribute(typeof(XmlBuilder)),
93     Designer("System.Web.UI.Design.WebControls.XmlDesigner, " + AssemblyRef.SystemDesign)
94     ]
95     public class Xml : Control {
96
97         private XPathNavigator _xpathNavigator;
98         private XmlDocument _document;
99         private XPathDocument _xpathDocument;
100 #pragma warning disable 0618    // To avoid deprecation warning
101         private XslTransform _transform;
102 #pragma warning restore 0618
103         private XslCompiledTransform _compiledTransform;
104         private XsltArgumentList _transformArgumentList;
105         private string _documentContent;
106         private string _documentSource;
107         private string _transformSource;
108
109         const string identityXslStr =
110             "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" +
111             "<xsl:template match=\"/\"> <xsl:copy-of select=\".\"/> </xsl:template> </xsl:stylesheet>";
112
113 #pragma warning disable 0618    // To avoid deprecation warning
114         static XslTransform _identityTransform;
115 #pragma warning restore 0618
116
117         [SuppressMessage("Microsoft.Security", "MSEC1201:DoNotUseXslTransform", Justification = "_identityTransform contents are trusted hard-coded string.")]
118         [SuppressMessage("Microsoft.Security.Xml", "CA3050:DoNotUseXslTransform", Justification = "_identityTransform contents are trusted hard-coded string.")]
119         [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "_identityTransform contents are trusted hard-coded string.")]
120         [SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification = "_identityTransform contents are trusted hard-coded string.")]
121         [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
122         static Xml() {
123
124             // Instantiate an identity transform, to be used whenever we need to output XML
125             XmlTextReader reader = new XmlTextReader(new StringReader(identityXslStr));
126 #pragma warning disable 0618    // To avoid deprecation warning
127             _identityTransform = new XslTransform();
128 #pragma warning restore 0618
129
130             _identityTransform.Load(reader, null /*resolver*/, null /*evidence*/);
131         }
132
133         [
134         EditorBrowsable(EditorBrowsableState.Never),
135         ]
136         public override string ClientID {
137             get {
138                 return base.ClientID;
139             }
140         }
141
142         [
143         EditorBrowsable(EditorBrowsableState.Never),
144         ]
145         public override ControlCollection Controls {
146             get {
147                 return base.Controls;
148             }
149         }
150
151
152         /// <devdoc>
153         ///    <para>[To be supplied.]</para>
154         /// </devdoc>
155         [
156         Browsable(false),
157         WebSysDescription(SR.Xml_Document),
158         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
159         Obsolete("The recommended alternative is the XPathNavigator property. Create a System.Xml.XPath.XPathDocument and call CreateNavigator() to create an XPathNavigator. http://go.microsoft.com/fwlink/?linkid=14202"),
160         ]
161         public XmlDocument Document {
162             get {
163                 if (_document == null) {
164                     LoadXmlDocument();
165                 }
166                 return _document;
167             }
168             set {
169                 DocumentSource = null;
170                 _xpathDocument = null;
171                 _documentContent = null;
172                 _document = value;
173             }
174         }
175
176
177         /// <devdoc>
178         ///    <para>[To be supplied.]</para>
179         /// </devdoc>
180         [
181         Browsable(false),
182         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
183         WebSysDescription(SR.Xml_DocumentContent)
184         ]
185         public String DocumentContent {
186             get {
187                 return _documentContent != null ? _documentContent : String.Empty;
188             }
189             set {
190                 _document = null;
191                 _xpathDocument = null;
192                 _xpathNavigator = null;
193                 _documentContent = value;
194
195                 if (DesignMode) {
196                     ViewState["OriginalContent"] = null;
197                 }
198             }
199         }
200
201
202         /// <devdoc>
203         ///    <para>[To be supplied.]</para>
204         /// </devdoc>
205         [
206         WebCategory("Behavior"),
207         DefaultValue(""),
208         Editor("System.Web.UI.Design.XmlUrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
209         UrlProperty(),
210         WebSysDescription(SR.Xml_DocumentSource)
211         ]
212         public String DocumentSource {
213             get {
214                 return (_documentSource == null) ? String.Empty : _documentSource;
215             }
216             set {
217                 _document = null;
218                 _xpathDocument = null;
219                 _documentContent = null;
220                 _xpathNavigator = null;
221                 _documentSource = value;
222             }
223         }
224
225         [
226         Browsable(false),
227         DefaultValue(false),
228         EditorBrowsable(EditorBrowsableState.Never),
229         ]
230         public override bool EnableTheming {
231             get {
232                 return false;
233             }
234             set {
235                 throw new NotSupportedException(SR.GetString(SR.NoThemingSupport, this.GetType().Name));
236             }
237         }
238
239         [
240         Browsable(false),
241         DefaultValue(""),
242         EditorBrowsable(EditorBrowsableState.Never),
243         ]
244         public override string SkinID {
245             get {
246                 return String.Empty;
247             }
248             set {
249                 throw new NotSupportedException(SR.GetString(SR.NoThemingSupport, this.GetType().Name));
250             }
251         }
252
253
254         /// <devdoc>
255         ///    <para>[To be supplied.]</para>
256         /// </devdoc>
257         [
258         Browsable(false),
259         WebSysDescription(SR.Xml_Transform),
260         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
261         ]
262 #pragma warning disable 0618    // To avoid deprecation warning
263         public XslTransform Transform {
264 #pragma warning restore 0618
265             get {
266                 return XmlUtils.GetXslTransform(_transform);
267             }
268             set {
269                 if (XmlUtils.GetXslTransform(value) != null) {
270                     // Setting this property will nullify _transform, so do it first.
271                     TransformSource = null;
272                     _transform = value;
273                 }
274             }
275         }
276
277
278         /// <devdoc>
279         ///    <para>[To be supplied.]</para>
280         /// </devdoc>
281         [
282         Browsable(false),
283         WebSysDescription(SR.Xml_TransformArgumentList),
284         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
285         ]
286         public XsltArgumentList TransformArgumentList {
287             get {
288                 return _transformArgumentList;
289             }
290             set {
291                 _transformArgumentList = value;
292             }
293         }
294
295
296         /// <devdoc>
297         ///    <para>[To be supplied.]</para>
298         /// </devdoc>
299         [
300         WebCategory("Behavior"),
301         DefaultValue(""),
302         Editor("System.Web.UI.Design.XslUrlEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
303         WebSysDescription(SR.Xml_TransformSource),
304         ]
305         public String TransformSource {
306             get {
307                 return (_transformSource == null) ? String.Empty : _transformSource;
308             }
309             set {
310                 _transform = null;
311                 _transformSource = value;
312             }
313         }
314
315
316         /// <devdoc>
317         /// </devdoc>
318         [
319         Browsable(false),
320         WebSysDescription(SR.Xml_XPathNavigator),
321         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
322         ]
323         public XPathNavigator XPathNavigator {
324             get {
325                 return _xpathNavigator;
326             }
327             set {
328                 DocumentSource = null;
329                 _xpathDocument = null;
330                 _documentContent = null;
331                 _document = null;
332                 _xpathNavigator = value;
333             }
334         }
335
336
337         [SuppressMessage("Microsoft.Security", "MSEC1218:ReviewWebControlForSet_DocumentContent", Justification = "Legacy code that trusts our developer input.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
338         [SuppressMessage("Microsoft.Security.Xml", "CA3067:ReviewWebControlForSet_DocumentContent", Justification = "Legacy code that trusts our developer input.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
339         protected override void AddParsedSubObject(object obj) {
340             if (obj is LiteralControl) {
341                 // Trim the initial whitespaces since XML is very picky (related to ASURT 58100)
342                 string text = ((LiteralControl)obj).Text;
343
344                 int iFirstNonWhiteSpace = Util.FirstNonWhiteSpaceIndex(text);
345                 DocumentContent = text.Substring(iFirstNonWhiteSpace);
346
347                 if (DesignMode) {
348                     ViewState["OriginalContent"] = text;
349                 }
350             }
351             else {
352                 throw new HttpException(SR.GetString(SR.Cannot_Have_Children_Of_Type, "Xml", obj.GetType().Name.ToString(CultureInfo.InvariantCulture)));
353             }
354         }
355
356         protected override ControlCollection CreateControlCollection() {
357             return new EmptyControlCollection(this);
358         }
359
360         [
361         EditorBrowsable(EditorBrowsableState.Never),
362         ]
363         public override Control FindControl(string id) {
364             return base.FindControl(id);
365         }
366
367
368         /// <devdoc>
369         /// </devdoc>
370         [
371         EditorBrowsable(EditorBrowsableState.Never),
372         ]
373         public override void Focus() {
374             throw new NotSupportedException(SR.GetString(SR.NoFocusSupport, this.GetType().Name));
375         }
376
377         [SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
378         protected override IDictionary GetDesignModeState() {
379             IDictionary designModeState = new HybridDictionary();
380             designModeState["OriginalContent"] = ViewState["OriginalContent"];
381
382             return designModeState;
383         }
384
385         [
386         EditorBrowsable(EditorBrowsableState.Never),
387         ]
388         public override bool HasControls() {
389             return base.HasControls();
390         }
391
392         private void LoadTransformFromSource() {
393
394             // We're done if we already have a transform
395             if (_transform != null)
396                 return;
397
398             if (String.IsNullOrEmpty(_transformSource) || _transformSource.Trim().Length == 0)
399                 return;
400
401             // First, figure out if it's a physical or virtual path
402             VirtualPath virtualPath;
403             string physicalPath;
404             ResolvePhysicalOrVirtualPath(_transformSource, out virtualPath, out physicalPath);
405
406             CacheInternal cacheInternal = HttpRuntime.CacheInternal;
407             string key = CacheInternal.PrefixLoadXPath + ((physicalPath != null) ?
408                 physicalPath : virtualPath.VirtualPathString);
409
410             object xform = cacheInternal.Get(key);
411
412             if (xform == null) {
413                 Debug.Trace("XmlControl", "Xsl Transform not found in cache (" + _transformSource + ")");
414
415                 // Get the stream, and open the doc
416                 CacheDependency dependency;
417                 using (Stream stream = OpenFileAndGetDependency(virtualPath, physicalPath, out dependency)) {
418
419                     // If we don't have a physical path, call MapPath to get one, in order to pass it as
420                     // the baseUri to XmlTextReader.  In pure VirtualPathProvider scenarios, it won't
421                     // help much, but it allows the default case to have relative references (VSWhidbey 545322)
422                     if (physicalPath == null)
423                         physicalPath = virtualPath.MapPath();
424
425                     // XslCompiledTransform for some reason wants to completely re-create an internal XmlReader
426                     // from scratch.  In doing so, it does not respect all the settings of XmlTextReader.  So we want
427                     // to be sure to specifically use XmlReader here so our settings on it will be respected by the
428                     // XslCompiledTransform.
429                     XmlReader xmlReader = XmlUtils.CreateXmlReader(stream, physicalPath);
430                     _transform = XmlUtils.CreateXslTransform(xmlReader);
431                     if (_transform == null) {
432                         _compiledTransform = XmlUtils.CreateXslCompiledTransform(xmlReader);
433                     }
434                 }
435
436                 // Cache it, but only if we got a dependency
437                 if (dependency != null) {
438                     using (dependency) {
439                         cacheInternal.UtcInsert(key, ((_compiledTransform == null) ? (object)_transform : (object)_compiledTransform), dependency);
440                     }
441                 }
442             }
443             else {
444                 Debug.Trace("XmlControl", "XslTransform found in cache (" + _transformSource + ")");
445
446                 _compiledTransform = xform as XslCompiledTransform;
447                 if (_compiledTransform == null) {
448 #pragma warning disable 0618    // To avoid deprecation warning
449                     _transform = (XslTransform)xform;
450 #pragma warning restore 0618
451                 }
452             }
453         }
454
455         private void LoadXmlDocument() {
456
457             Debug.Assert(_xpathDocument == null && _document == null && _xpathNavigator == null);
458
459             if (!String.IsNullOrEmpty(_documentContent)) {
460                 _document = XmlUtils.CreateXmlDocumentFromContent(_documentContent);
461                 return;
462             }
463
464             if (String.IsNullOrEmpty(_documentSource))
465                 return;
466
467             // Make it absolute and check security
468             string physicalPath = MapPathSecure(_documentSource);
469
470             CacheInternal cacheInternal = System.Web.HttpRuntime.CacheInternal;
471             string key = CacheInternal.PrefixLoadXml + physicalPath;
472
473             _document = (XmlDocument) cacheInternal.Get(key);
474
475             if (_document == null) {
476                 Debug.Trace("XmlControl", "XmlDocument not found in cache (" + _documentSource + ")");
477
478                 CacheDependency dependency;
479                 using (Stream stream = OpenFileAndGetDependency(null, physicalPath, out dependency)) {
480
481                     _document = new XmlDocument();
482                     _document.Load(XmlUtils.CreateXmlReader(stream, physicalPath));
483                     cacheInternal.UtcInsert(key, _document, dependency);
484                 }
485             }
486             else {
487                 Debug.Trace("XmlControl", "XmlDocument found in cache (" + _documentSource + ")");
488             }
489
490             // 
491             lock (_document) {
492                 // Always return a clone of the cached copy
493                 _document = (XmlDocument)_document.CloneNode(true/*deep*/);
494             }
495         }
496
497         private void LoadXPathDocument() {
498
499             Debug.Assert(_xpathDocument == null && _document == null && _xpathNavigator == null);
500
501             if (!String.IsNullOrEmpty(_documentContent)) {
502                 _xpathDocument = XmlUtils.CreateXPathDocumentFromContent(_documentContent);
503                 return;
504             }
505
506             if (String.IsNullOrEmpty(_documentSource))
507                 return;
508
509             // First, figure out if it's a physical or virtual path
510             VirtualPath virtualPath;
511             string physicalPath;
512             ResolvePhysicalOrVirtualPath(_documentSource, out virtualPath, out physicalPath);
513
514             CacheInternal cacheInternal = HttpRuntime.CacheInternal;
515             string key = CacheInternal.PrefixLoadXPath + ((physicalPath != null) ?
516                 physicalPath : virtualPath.VirtualPathString);
517
518             _xpathDocument = (XPathDocument)cacheInternal.Get(key);
519             if (_xpathDocument == null) {
520                 Debug.Trace("XmlControl", "XPathDocument not found in cache (" + _documentSource + ")");
521
522                 // Get the stream, and open the doc
523                 CacheDependency dependency;
524                 using (Stream stream = OpenFileAndGetDependency(virtualPath, physicalPath, out dependency)) {
525                     // The same comments as in LoadTransformFromSource() (VSWhidbey 545322, 546662)
526                     if (physicalPath == null) {
527                         physicalPath = virtualPath.MapPath();
528                     }
529   
530                     _xpathDocument = new XPathDocument(XmlUtils.CreateXmlReader(stream, physicalPath));
531                 }
532
533                 // Cache it, but only if we got a dependency
534                 if (dependency != null) {
535                     using (dependency) {
536                         cacheInternal.UtcInsert(key, _xpathDocument, dependency);
537                     }
538                 }
539             }
540             else {
541                 Debug.Trace("XmlControl", "XPathDocument found in cache (" + _documentSource + ")");
542             }
543         }
544
545
546         /// <devdoc>
547         ///    <para>[To be supplied.]</para>
548         /// </devdoc>
549         [SuppressMessage("Microsoft.Security", "MSEC1204:UseSecureXmlResolver", Justification = "Legacy code that trusts our developer input.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
550         protected internal override void Render(HtmlTextWriter output)
551         {
552
553             // If we don't already have an XmlDocument or an XPathNavigator, load am XPathDocument (which is faster)
554             if ((_document == null) && (_xpathNavigator == null)) {
555                 LoadXPathDocument();
556             }
557
558             LoadTransformFromSource();
559
560             // Abort if nothing has been loaded
561             if (_document == null && _xpathDocument == null && _xpathNavigator == null) {
562                 return;
563             }
564
565             // If we don't have a transform, use the identity transform, which
566             // simply renders the XML.
567             if (_transform == null)
568                 _transform = _identityTransform;
569
570             // Pass a resolver in full trust, to support certain XSL scenarios (ASURT 141427)
571             XmlUrlResolver xr = null;
572             if (HttpRuntime.HasUnmanagedPermission()) {
573                 xr = new XmlUrlResolver();
574             }
575
576             IXPathNavigable doc = null;
577             if (_document != null) {
578                 doc = _document;
579             } else if (_xpathNavigator != null) {
580                 doc = _xpathNavigator;
581             } else {
582                 doc = _xpathDocument;
583             }
584
585             if (_compiledTransform != null) {
586                 XmlWriter writer = XmlWriter.Create(output);
587                 _compiledTransform.Transform(doc, _transformArgumentList, writer, null);
588             } else {
589                 _transform.Transform(doc, _transformArgumentList, output, xr);
590             }
591         }
592     }
593 }
594