diff --git a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs index 70fff6deaa..73ec63d9c4 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/XmlDataContractSerializerInputFormatter.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; +using System.Text; using System.Threading.Tasks; using System.Xml; using Microsoft.AspNet.Mvc.Internal; @@ -95,7 +96,11 @@ namespace Microsoft.AspNet.Mvc.Xml { var request = context.ActionContext.HttpContext.Request; - using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body))) + MediaTypeHeaderValue requestContentType; + MediaTypeHeaderValue.TryParse(request.ContentType , out requestContentType); + var effectiveEncoding = SelectCharacterEncoding(requestContentType); + + using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding)) { var type = GetSerializableType(context.ModelType); @@ -131,11 +136,11 @@ namespace Microsoft.AspNet.Mvc.Xml /// Called during deserialization to get the . /// /// The from which to read. + /// The used to read the stream. /// The used during deserialization. - protected virtual XmlReader CreateXmlReader([NotNull] Stream readStream) + protected virtual XmlReader CreateXmlReader([NotNull] Stream readStream, [NotNull] Encoding encoding) { - return XmlDictionaryReader.CreateTextReader( - readStream, _readerQuotas); + return XmlDictionaryReader.CreateTextReader(readStream, encoding, _readerQuotas, onClose: null); } /// diff --git a/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs index 78a149e539..4aaaebe095 100644 --- a/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Xml/XmlSerializerInputFormatter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Text; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; @@ -71,7 +72,11 @@ namespace Microsoft.AspNet.Mvc.Xml { var request = context.ActionContext.HttpContext.Request; - using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body))) + MediaTypeHeaderValue requestContentType; + MediaTypeHeaderValue.TryParse(request.ContentType, out requestContentType); + var effectiveEncoding = SelectCharacterEncoding(requestContentType); + + using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), effectiveEncoding)) { var type = GetSerializableType(context.ModelType); @@ -116,11 +121,11 @@ namespace Microsoft.AspNet.Mvc.Xml /// Called during deserialization to get the . /// /// The from which to read. + /// The used to read the stream. /// The used during deserialization. - protected virtual XmlReader CreateXmlReader([NotNull] Stream readStream) + protected virtual XmlReader CreateXmlReader([NotNull] Stream readStream, [NotNull] Encoding encoding) { - return XmlDictionaryReader.CreateTextReader( - readStream, _readerQuotas); + return XmlDictionaryReader.CreateTextReader(readStream, encoding, _readerQuotas, onClose: null); } /// diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs index 680e2c2a68..bfde8721ef 100644 --- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs @@ -289,15 +289,15 @@ namespace Microsoft.AspNet.Mvc.Xml } [Fact] - public async Task ReadAsync_ThrowsOnInvalidCharacters() + public async Task ReadAsync_FallsbackToUTF8_WhenCharSet_NotInContentType() { // Arrange var expectedException = TestPlatformHelper.IsMono ? typeof(SerializationException) : typeof(XmlException); var expectedMessage = TestPlatformHelper.IsMono ? "Expected element 'TestLevelTwo' in namespace '', but found Element node 'DummyClass' in namespace ''" : - "The encoding in the declaration 'UTF-8' does not match the encoding of the document 'utf-16LE'."; - var inpStart = Encodings.UTF16EncodingLittleEndian.GetBytes("" + + "The expected encoding 'utf-8' does not match the actual encoding 'utf-16LE'."; + var inpStart = Encodings.UTF16EncodingLittleEndian.GetBytes("" + ""); byte[] inp = { 192, 193 }; var inpEnd = Encodings.UTF16EncodingLittleEndian.GetBytes(""); @@ -315,6 +315,27 @@ namespace Microsoft.AspNet.Mvc.Xml Assert.Equal(expectedMessage, ex.Message); } + [Fact] + public async Task ReadAsync_UsesContentTypeCharSet_ToReadStream() + { + // Arrange + var expectedException = TestPlatformHelper.IsMono ? typeof(SerializationException) : + typeof(XmlException); + var expectedMessage = TestPlatformHelper.IsMono ? + "Expected element 'TestLevelTwo' in namespace '', but found Element node 'DummyClass' in namespace ''" : + "The expected encoding 'utf-16LE' does not match the actual encoding 'utf-8'."; + var inputBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes("" + + "1000"); + + var formatter = new XmlDataContractSerializerInputFormatter(); + var actionContext = GetActionContext(inputBytes, contentType: "application/xml; charset=utf-16"); + var context = new InputFormatterContext(actionContext, typeof(TestLevelOne)); + + // Act + var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context)); + Assert.Equal(expectedMessage, ex.Message); + } + [Fact] public async Task ReadAsync_IgnoresBOMCharacters() { @@ -359,8 +380,10 @@ namespace Microsoft.AspNet.Mvc.Xml var formatter = new XmlDataContractSerializerInputFormatter(); var contentBytes = Encodings.UTF16EncodingLittleEndian.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); + var actionContext = GetActionContext(contentBytes, contentType: "application/xml; charset=utf-16"); + var context = new InputFormatterContext(actionContext, typeof(TestLevelOne)); + // Act var model = await formatter.ReadAsync(context); diff --git a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs index cb103062bd..212e6c48e3 100644 --- a/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.Xml.Test/XmlSerializerInputFormatterTest.cs @@ -296,16 +296,16 @@ namespace Microsoft.AspNet.Mvc.Xml } [Fact] - public async Task ReadAsync_ThrowsOnInvalidCharacters() + public async Task ReadAsync_FallsbackToUTF8_WhenCharSet_NotInContentType() { // Arrange var expectedException = TestPlatformHelper.IsMono ? typeof(InvalidOperationException) : typeof(XmlException); var expectedMessage = TestPlatformHelper.IsMono ? "There is an error in XML document." : - "The encoding in the declaration 'UTF-8' does not match the encoding of the document 'utf-16LE'."; + "The expected encoding 'utf-8' does not match the actual encoding 'utf-16LE'."; - var inpStart = Encodings.UTF16EncodingLittleEndian.GetBytes("" + + var inpStart = Encodings.UTF16EncodingLittleEndian.GetBytes("" + ""); byte[] inp = { 192, 193 }; var inpEnd = Encodings.UTF16EncodingLittleEndian.GetBytes(""); @@ -323,6 +323,28 @@ namespace Microsoft.AspNet.Mvc.Xml Assert.Equal(expectedMessage, ex.Message); } + [Fact] + public async Task ReadAsync_UsesContentTypeCharSet_ToReadStream() + { + // Arrange + var expectedException = TestPlatformHelper.IsMono ? typeof(InvalidOperationException) : + typeof(XmlException); + var expectedMessage = TestPlatformHelper.IsMono ? + "There is an error in XML document." : + "The expected encoding 'utf-16LE' does not match the actual encoding 'utf-8'."; + + var inputBytes = Encodings.UTF8EncodingWithoutBOM.GetBytes("" + + "1000"); + + var formatter = new XmlSerializerInputFormatter(); + var actionContext = GetActionContext(inputBytes, contentType: "application/xml; charset=utf-16"); + var context = new InputFormatterContext(actionContext, typeof(TestLevelOne)); + + // Act and Assert + var ex = await Assert.ThrowsAsync(expectedException, () => formatter.ReadAsync(context)); + Assert.Equal(expectedMessage, ex.Message); + } + [Fact] public async Task ReadAsync_IgnoresBOMCharacters() { @@ -369,7 +391,9 @@ namespace Microsoft.AspNet.Mvc.Xml var formatter = new XmlSerializerInputFormatter(); var contentBytes = Encodings.UTF16EncodingLittleEndian.GetBytes(input); - var context = GetInputFormatterContext(contentBytes, typeof(TestLevelOne)); + + var actionContext = GetActionContext(contentBytes, contentType: "application/xml; charset=utf-16"); + var context = new InputFormatterContext(actionContext, typeof(TestLevelOne)); // Act var model = await formatter.ReadAsync(context); @@ -398,6 +422,7 @@ namespace Microsoft.AspNet.Mvc.Xml new AspNet.Routing.RouteData(), new ActionDescriptor()); } + private static HttpContext GetHttpContext(byte[] contentBytes, string contentType = "application/xml") {