diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs index 182f3562eb..3f2cec8f5e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using System.Text; using System.Xml; using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient.Internal; @@ -32,6 +33,13 @@ internal static class SqlTypeWorkarounds private static readonly XmlReaderSettings s_defaultXmlReaderSettingsCloseInput = new() { ConformanceLevel = ConformanceLevel.Fragment, CloseInput = true }; private static readonly XmlReaderSettings s_defaultXmlReaderSettingsAsyncCloseInput = new() { Async = true, ConformanceLevel = ConformanceLevel.Fragment, CloseInput = true }; + private static readonly XmlParserContext s_defaultXmlParserContext = new( + nt: null, nsMgr: null, xmlLang: string.Empty, xmlSpace: XmlSpace.None, + // Do not use SqlUnicodeEncoding here. It assumes no BOM, but some streams backing + // XmlReader (specifically, SqlStream) can have artificial BOMs. Consider removing + // artificial BOM behavior from SqlStream as a future optimization. + Encoding.Unicode); + internal const SqlCompareOptions SqlStringValidSqlCompareOptionMask = SqlCompareOptions.BinarySort | SqlCompareOptions.BinarySort2 | @@ -50,7 +58,7 @@ internal static XmlReader SqlXmlCreateSqlXmlReader(Stream stream, bool closeInpu : s_defaultXmlReaderSettingsCloseInput : s_defaultXmlReaderSettings; - return XmlReader.Create(stream, settingsToUse); + return XmlReader.Create(stream, settingsToUse, s_defaultXmlParserContext); } #endregion diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs index cf85112559..6db13f2670 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs @@ -89,7 +89,7 @@ public static async Task GetFieldValueAsync_OfXmlReader(CommandBehavior behavior using (XmlReader xmlReader = await reader.GetFieldValueAsync(1)) { isAsync = xmlReader.Settings.Async; - outputXml = GetXmlDocumentContents(xmlReader); + outputXml = GetXmlReaderContents(xmlReader); } } } @@ -207,7 +207,7 @@ public static async Task GetFieldValue_OfXmlReader(CommandBehavior behavior, boo using (XmlReader xmlReader = reader.GetFieldValue(1)) { isAsync = xmlReader.Settings.Async; - outputXml = GetXmlDocumentContents(xmlReader); + outputXml = GetXmlReaderContents(xmlReader); } } } @@ -364,7 +364,7 @@ public static async Task GetXmlReader(CommandBehavior behavior, bool isExecuteAs using (XmlReader xmlReader = reader.GetXmlReader(1)) { isAsync = xmlReader.Settings.Async; - outputXml = GetXmlDocumentContents(xmlReader); + outputXml = GetXmlReaderContents(xmlReader); } } } @@ -481,7 +481,7 @@ public static void NullStreamProperties(CommandBehavior behavior, AccessorType a // get a clean reader over the same field and check that the value is empty using (XmlReader xmlReader = GetValue(reader, 0, accessorType)) { - Assert.Equal(GetXmlDocumentContents(xmlReader), string.Empty); + Assert.Equal(GetXmlReaderContents(xmlReader), string.Empty); } using (TextReader textReader = GetValue(reader, 1, accessorType)) @@ -1169,6 +1169,11 @@ private static string CreateXmlData(int packetSize, int forcedPacketCount) using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings)) { int index = 1; + // Purposefully start with root-level text to confuse the encoding-detection logic + // in XmlTextReaderImpl so it would assume UTF-8. This ensures tests will fail if a + // SqlDataReader creates an XmlReader over a UTF-16LE stream without explicitly + // specifying the encoding. + xmlWriter.WriteString("foo"); xmlWriter.WriteStartElement("root"); while (buffer.Length / 2 < (packetSize * forcedPacketCount)) { @@ -1232,15 +1237,19 @@ private static string CreateCharDataQuery(string originalText) return queryBuilder.ToString(); } - private static string GetXmlDocumentContents(XmlReader xmlReader) + /// + /// Serializes the contents of the specified to a + /// . + /// + /// The reader whose contents are to be serialized. + /// + /// A string representation of the contents of . + /// + private static string GetXmlReaderContents(XmlReader xmlReader) { - string outputXml; - XmlDocument document = new XmlDocument(); - document.Load(xmlReader); - XmlWriterSettings settings = new XmlWriterSettings { - ConformanceLevel = ConformanceLevel.Document, + ConformanceLevel = ConformanceLevel.Fragment, Encoding = Encoding.Unicode, Indent = true, OmitXmlDeclaration = true @@ -1250,10 +1259,9 @@ private static string GetXmlDocumentContents(XmlReader xmlReader) using (StringWriter stringWriter = new StringWriter(buffer)) using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings)) { - document.WriteContentTo(xmlWriter); + xmlWriter.WriteNode(xmlReader, defattr: false); } - outputXml = buffer.ToString(); - return outputXml; + return buffer.ToString(); } private static byte[] GetStreamContents(Stream stream)