//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Util { using System.IO; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; using System.Diagnostics.CodeAnalysis; internal static class XmlUtils { public static readonly long MaxEntityExpansion = 1024 * 1024; [SuppressMessage("Microsoft.Security", "MSEC1208:DoNotUseLoadXml", Justification = "Handles developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public static XmlDocument CreateXmlDocumentFromContent(string content) { XmlDocument doc = new XmlDocument(); if (AppSettings.RestrictXmlControls) { // We can't use the simple XmlDocument.LoadXml(string) here because there is no way to control the // resolver used in that process. The only way we can do that is if we pass in our own XmlReader. using (StringReader sreader = new StringReader(content)) { doc.Load(CreateXmlReader(sreader)); } } else { doc.LoadXml(content); } return doc; } [SuppressMessage("Microsoft.Security", "MSEC1210:UseXmlReaderForXPathDocument", Justification = "Handles developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public static XPathDocument CreateXPathDocumentFromContent(string content) { StringReader reader = new StringReader(content); if (AppSettings.RestrictXmlControls) { return new XPathDocument(CreateXmlReader(reader)); } else { return new XPathDocument(reader); } } [SuppressMessage("Microsoft.Security", "MSEC1220:ReviewDtdProcessingAssignment", Justification = "Dtd processing is needed for back-compat, but is being done as safely as possible.")] public static XmlReaderSettings CreateXmlReaderSettings() { XmlReaderSettings settings = new XmlReaderSettings(); if (AppSettings.RestrictXmlControls) { settings.MaxCharactersFromEntities = XmlUtils.MaxEntityExpansion; settings.XmlResolver = null; // Prohibit is the default here. We don't need to prohibit DTD's, or even ignore them if we're using // RestrictXmlControls, because we use a null resolver and limit/disable entity expansion. settings.DtdProcessing = DtdProcessing.Parse; } return settings; } // Ideally, these XmlReader factories would use XmlReader.Create() in the non-RestrictXmlControls case, // but the default settings on that method are different from the default settings generated by XmlTextReader // constructors, which is the code we're replacing with these factories. Since we want to keep doing // whatever it was that we did before in this case, we'll just new up an XmlTextReader rather than // try to guess at how to set matching defaults with XmlReader.Create(). // (E.g. DtdProcessing is Parse by default using XmlTextReader directly. It's Prohibit in default XmlReaderSettings.) [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")] public static XmlReader CreateXmlReader(string filepath) { if (AppSettings.RestrictXmlControls) { NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(filepath); return XmlReader.Create(nextr, CreateXmlReaderSettings()); } else { XmlTextReader xtr = new XmlTextReader(filepath); return xtr; } } [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")] public static XmlReader CreateXmlReader(Stream datastream) { if (AppSettings.RestrictXmlControls) { NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(datastream); return XmlReader.Create(nextr, CreateXmlReaderSettings()); } else { XmlTextReader xtr = new XmlTextReader(datastream); return xtr; } } [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")] public static XmlReader CreateXmlReader(TextReader reader) { if (AppSettings.RestrictXmlControls) { NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(reader); return XmlReader.Create(nextr, CreateXmlReaderSettings()); } else { XmlTextReader xtr = new XmlTextReader(reader); return xtr; } } [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles trusted or developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] [SuppressMessage("Microsoft.Security", "MSEC1225:ReviewClassesDerivedFromXmlTextReader", Justification = "NoEntitiesXmlReader is our internal mechanism for using XmlTextReaders in a reasonably safe manner.")] public static XmlReader CreateXmlReader(Stream contentStream, string baseURI) { if (AppSettings.RestrictXmlControls) { NoEntitiesXmlTextReader nextr = new NoEntitiesXmlTextReader(baseURI, contentStream); return XmlReader.Create(nextr, CreateXmlReaderSettings()); } else { XmlTextReader xtr = new XmlTextReader(baseURI, contentStream); return xtr; } } // If you use any of these overloads that take in XmlReaderSettings, the suggestion is to get your base settings from // CreateXmlReaderSettings(). That way you will have the correct defaults for RestrictXmlControls if applicable. // Then you need to be smart about which settings you change before passing in here, because we will not // re-enforce the correct settings, just in case you intentionally meant to change them. public static XmlReader CreateXmlReader(TextReader reader, string baseURI, XmlReaderSettings settings) { if (settings == null) { settings = CreateXmlReaderSettings(); } // Note: If there is nothing materially changed in the settings, then Create() will just return your reader back // to you and reader.Settings might still be null. return XmlReader.Create(reader, settings, baseURI); } public static XslCompiledTransform CreateXslCompiledTransform(XmlReader xmlReader) { XmlReader readerToUse = xmlReader; // XslCompiledTransform reconstructs its own XmlReader from scratch, so we can't rely entirely on the fancy // protections we have in place on our readers. We need to bring out a bigger hammer and disable DTD's // alltogether. We know how to work with XmlTextReader and which of its settings XslCompiledTransform will // respect. For other reader types, just try to wrap it with new settings. if (AppSettings.RestrictXmlControls) { XmlTextReader xtr = xmlReader as XmlTextReader; if (xtr != null) { xtr.DtdProcessing = DtdProcessing.Ignore; } else { XmlReaderSettings settings = xmlReader.Settings; if (settings == null) { settings = CreateXmlReaderSettings(); } settings.DtdProcessing = DtdProcessing.Ignore; readerToUse = XmlReader.Create(xmlReader, settings); } } XslCompiledTransform compiledTransform = new XslCompiledTransform(); // The second parameter is XsltSettings. null results in the XsltSettings.Default being used, which disables the document function, and script // The third parameter is an XmlResolver to be used compiledTransform.Load(readerToUse, null, null); return compiledTransform; } #pragma warning disable 0618 // To avoid deprecation warning [SuppressMessage("Microsoft.Security", "MSEC1201:DoNotUseXslTransform", Justification = "Handles developer-controlled input xsl. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public static XslTransform CreateXslTransform(XmlReader reader) { if (!AppSettings.RestrictXmlControls) { XslTransform xform = new XslTransform(); xform.Load(reader); return xform; } return null; } [SuppressMessage("Microsoft.Security", "MSEC1201:DoNotUseXslTransform", Justification = "Handles developer-controlled input xsl. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public static XslTransform CreateXslTransform(XmlReader reader, XmlResolver resolver) { if (!AppSettings.RestrictXmlControls) { XslTransform xform = new XslTransform(); xform.Load(reader, resolver, null); return xform; } return null; } public static XslTransform GetXslTransform(XslTransform xform) { return (AppSettings.RestrictXmlControls ? null : xform); } #pragma warning restore 0618 // This class exists to override the ResolveEntity() method, which can be used to force resolution of custom/external // entities in Xml files. When we use this class, we should have already set EntityHandling to EntityHandling.ExpandCharEntities, // which will disable custom/external entity expansion by default. But this extra protection will keep people from unintentionally // shooting themselves in the foot when they think they might have been safe. private sealed class NoEntitiesXmlTextReader : XmlTextReader { [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public NoEntitiesXmlTextReader() : base() { Restrict(); } [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public NoEntitiesXmlTextReader(string filepath) : base(filepath) { Restrict(); } [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public NoEntitiesXmlTextReader(TextReader reader) : base(reader) { Restrict(); } [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public NoEntitiesXmlTextReader(Stream datastream) : base(datastream) { Restrict(); } [SuppressMessage("Microsoft.Security", "MSEC1205:DoNotAllowDtdOnXmlTextReader", Justification = "Handles developer-controlled input xml. Optional safer codepath available via appSettings/aspnet:RestrictXmlControls configuration.")] public NoEntitiesXmlTextReader(string baseURI, Stream contentStream) : base(baseURI, contentStream) { Restrict(); } public override void ResolveEntity() { // Do not ever do general entity expansion/replacement, even when asked return; } private void Restrict() { EntityHandling = EntityHandling.ExpandCharEntities; XmlResolver = null; } } } }