Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.Web / Util / XmlUtils.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlUtils.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.Util {
8
9     using System.IO;
10     using System.Xml;
11     using System.Xml.XPath;
12     using System.Xml.Xsl;
13     using System.Diagnostics.CodeAnalysis;
14
15     internal static class XmlUtils
16     {
17         public static readonly long MaxEntityExpansion = 1024 * 1024;
18
19         [SuppressMessage("Microsoft.Security", "MSEC1208:DoNotUseLoadXml", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
20         [SuppressMessage("Microsoft.Security.Xml", "CA3057:DoNotUseLoadXml", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
21         public static XmlDocument CreateXmlDocumentFromContent(string content)
22         {
23             XmlDocument doc = new XmlDocument();
24
25            if (AppSettings.RestrictXmlControls) {
26                 // We can't use the simple XmlDocument.LoadXml(string) here because there is no way to control the
27                 // resolver used in that process.  The only way we can do that is if we pass in our own XmlReader.
28                 using (StringReader sreader = new StringReader(content)) {
29                     doc.Load(CreateXmlReader(sreader));
30                 }
31             }
32             else {
33                 doc.LoadXml(content);
34             }
35             return doc;
36         }
37
38         [SuppressMessage("Microsoft.Security", "MSEC1210:UseXmlReaderForXPathDocument", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
39         [SuppressMessage("Microsoft.Security.Xml", "CA3059:UseXmlReaderForXPathDocument", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
40         public static XPathDocument CreateXPathDocumentFromContent(string content)
41         {
42             StringReader reader = new StringReader(content);
43             if (AppSettings.RestrictXmlControls) {
44                 return new XPathDocument(CreateXmlReader(reader));
45             }
46             else {
47                 return new XPathDocument(reader);
48             }
49         }
50
51         [SuppressMessage("Microsoft.Security", "MSEC1220:ReviewDtdProcessingAssignment", Justification = "Dtd processing is needed for back-compat, but is being done as safely as possible.")]
52         [SuppressMessage("Microsoft.Security.Xml", "CA3069:ReviewDtdProcessingAssignment", Justification = "Dtd processing is needed for back-compat, but is being done as safely as possible.")]
53         public static XmlReaderSettings CreateXmlReaderSettings()
54         {
55             XmlReaderSettings settings = new XmlReaderSettings();
56             if (AppSettings.RestrictXmlControls)
57             {
58                 settings.MaxCharactersFromEntities = XmlUtils.MaxEntityExpansion;
59                 settings.XmlResolver = null;
60                 // Prohibit is the default here.  We don't need to prohibit DTD's, or even ignore them if we're using
61                 // RestrictXmlControls, because we use a null resolver and limit/disable entity expansion.
62                 settings.DtdProcessing = DtdProcessing.Parse;
63             }
64             return settings;
65         }
66
67         // Ideally, these XmlReader factories would use XmlReader.Create() in the non-RestrictXmlControls case,
68         // but the default settings on that method are different from the default settings generated by XmlTextReader
69         // constructors, which is the code we're replacing with these factories.  Since we want to keep doing
70         // whatever it was that we did before in this case, we'll just new up an XmlTextReader rather than
71         // try to guess at how to set matching defaults with XmlReader.Create().
72         // (E.g. DtdProcessing is Parse by default using XmlTextReader directly.  It's Prohibit in default XmlReaderSettings.)
73         [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
74         [SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
75         [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
76         [SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
77         public static XmlReader CreateXmlReader(string filepath)
78         {
79             if (AppSettings.RestrictXmlControls)
80             {
81                 NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(filepath);
82                 return XmlReader.Create(nextr, CreateXmlReaderSettings());
83             }
84             else
85             {
86                 XmlTextReader xtr = new XmlTextReader(filepath);
87                 return xtr;
88             }
89         }
90
91         [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
92         [SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
93         [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
94         [SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
95         public static XmlReader CreateXmlReader(Stream datastream)
96         {
97             if (AppSettings.RestrictXmlControls)
98             {
99                 NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(datastream);
100                 return XmlReader.Create(nextr, CreateXmlReaderSettings());
101             }
102             else
103             {
104                 XmlTextReader xtr = new XmlTextReader(datastream);
105                 return xtr;
106             }
107         }
108
109         [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
110         [SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
111         [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
112         [SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
113         public static XmlReader CreateXmlReader(TextReader reader)
114         {
115             if (AppSettings.RestrictXmlControls)
116             {
117                 NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(reader);
118                 return XmlReader.Create(nextr, CreateXmlReaderSettings());
119             }
120             else
121             {
122                 XmlTextReader xtr = new XmlTextReader(reader);
123                 return xtr;
124             }
125         }
126
127         [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
128         [SuppressMessage("Microsoft.Security.Xml", "CA3054:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
129         [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
130         [SuppressMessage("Microsoft.Security.Xml", "CA3074:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")]
131         public static XmlReader CreateXmlReader(Stream contentStream, string baseURI)
132         {
133             if (AppSettings.RestrictXmlControls)
134             {
135                 NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(baseURI, contentStream);
136                 return XmlReader.Create(nextr, CreateXmlReaderSettings());
137             }
138             else
139             {
140                 XmlTextReader xtr = new XmlTextReader(baseURI, contentStream);
141                 return xtr;
142             }
143         }
144
145         // If you use any of these overloads that take in XmlReaderSettings, the suggestion is to get your base settings from
146         // CreateXmlReaderSettings().  That way you will have the correct defaults for RestrictXmlControls if applicable.
147         // Then you need to be smart about which settings you change before passing in here, because we will not
148         // re-enforce the correct settings, just in case you intentionally meant to change them.
149         public static XmlReader CreateXmlReader(TextReader reader, string baseURI, XmlReaderSettings settings)
150         {
151             if (settings == null) {
152                 settings = CreateXmlReaderSettings();
153             }
154
155             // Note:  If there is nothing materially changed in the settings, then Create() will just return your reader back
156             // to you and reader.Settings might still be null.
157             return XmlReader.Create(reader, settings, baseURI);
158         }
159
160         public static XslCompiledTransform CreateXslCompiledTransform(XmlReader xmlReader)
161         {
162             XmlReader readerToUse = xmlReader;
163
164             // XslCompiledTransform reconstructs its own XmlReader from scratch, so we can't rely entirely on the fancy
165             // protections we have in place on our readers.  We need to bring out a bigger hammer and disable DTD's
166             // alltogether.  We know how to work with XmlTextReader and which of its settings XslCompiledTransform will
167             // respect.  For other reader types, just try to wrap it with new settings.
168             if (AppSettings.RestrictXmlControls) {
169                 XmlTextReader xtr = xmlReader as XmlTextReader;
170                 if (xtr != null) {
171                     xtr.DtdProcessing = DtdProcessing.Ignore;
172                 }
173                 else {
174                     XmlReaderSettings settings = xmlReader.Settings;
175                     if (settings == null) {
176                         settings = CreateXmlReaderSettings();
177                     }
178                     settings.DtdProcessing = DtdProcessing.Ignore;
179                     readerToUse = XmlReader.Create(xmlReader, settings);
180                 }
181             }
182
183             XslCompiledTransform compiledTransform = new XslCompiledTransform();
184             // The second parameter is XsltSettings.  null results in the XsltSettings.Default being used, which disables the document function, and script
185             // The third parameter is an XmlResolver to be used
186             compiledTransform.Load(readerToUse, null, null);
187             return compiledTransform;
188         }
189
190
191 #pragma warning disable 0618    // To avoid deprecation warning
192         [SuppressMessage("Microsoft.Security", "MSEC1201:DoNotUseXslTransform", Justification = "Handles developer-controlled input xsl.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
193         [SuppressMessage("Microsoft.Security.Xml", "CA3050:DoNotUseXslTransform", Justification = "Handles developer-controlled input xsl.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
194         public static XslTransform CreateXslTransform(XmlReader reader)
195         {
196             if (!AppSettings.RestrictXmlControls)
197             {
198                 XslTransform xform = new XslTransform();
199                 xform.Load(reader);
200                 return xform;
201             }
202             return null;
203         }
204
205         [SuppressMessage("Microsoft.Security", "MSEC1201:DoNotUseXslTransform", Justification = "Handles developer-controlled input xsl.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
206         [SuppressMessage("Microsoft.Security.Xml", "CA3050:DoNotUseXslTransform", Justification = "Handles developer-controlled input xsl.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
207         public static XslTransform CreateXslTransform(XmlReader reader, XmlResolver resolver)
208         {
209             if (!AppSettings.RestrictXmlControls)
210             {
211                 XslTransform xform = new XslTransform();
212                 xform.Load(reader, resolver, null);
213                 return xform;
214             }
215             return null;
216         }
217
218         public static XslTransform GetXslTransform(XslTransform xform)
219         {
220             return (AppSettings.RestrictXmlControls ? null : xform);
221         }
222 #pragma warning restore 0618
223
224         // This class exists to override the ResolveEntity() method, which can be used to force resolution of custom/external
225         // entities in Xml files.  When we use this class, we should have already set EntityHandling to EntityHandling.ExpandCharEntities,
226         // which will disable custom/external entity expansion by default.  But this extra protection will keep people from unintentionally
227         // shooting themselves in the foot when they think they might have been safe.
228         private sealed class NoEntitiesXmlTextReader : XmlTextReader
229         {
230             [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
231             public NoEntitiesXmlTextReader() : base() { Restrict(); }
232             [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
233             public NoEntitiesXmlTextReader(string filepath) : base(filepath) { Restrict(); }
234             [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
235             public NoEntitiesXmlTextReader(TextReader reader) : base(reader) { Restrict(); }
236             [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
237             public NoEntitiesXmlTextReader(Stream datastream) : base(datastream) { Restrict(); }
238             [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml.  Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")]
239             public NoEntitiesXmlTextReader(string baseURI, Stream contentStream) : base(baseURI, contentStream) { Restrict(); }
240
241             public override void ResolveEntity()
242             {
243                 // Do not ever do general entity expansion/replacement, even when asked
244                 return;
245             }
246
247             private void Restrict() {
248                 EntityHandling = EntityHandling.ExpandCharEntities;
249                 XmlResolver = null;
250             }
251         }
252     }
253 }