94b2861243
Former-commit-id: 5f9c6ae75f295e057a7d2971f3a6df4656fa8850
254 lines
17 KiB
C#
254 lines
17 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="XmlUtils.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3057: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3059: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3069: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.Xml", "CA3054: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3074: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.Xml", "CA3054: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3074: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.Xml", "CA3054: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3074: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.Xml", "CA3054: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3074: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3050: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.")]
|
|
[SuppressMessage("Microsoft.Security.Xml", "CA3050: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;
|
|
}
|
|
}
|
|
}
|
|
}
|