// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters.Xml; using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.WebUtilities; namespace Microsoft.AspNetCore.Mvc.Formatters { /// /// This class handles deserialization of input XML data /// to strongly-typed objects using /// public class XmlSerializerInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy { private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); private readonly bool _suppressInputFormatterBuffering; private readonly MvcOptions _options; /// /// Initializes a new instance of XmlSerializerInputFormatter. /// [Obsolete("This constructor is obsolete and will be removed in a future version.")] public XmlSerializerInputFormatter() { SupportedEncodings.Add(UTF8EncodingWithoutBOM); SupportedEncodings.Add(UTF16EncodingLittleEndian); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml); SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax); WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); } /// /// Initializes a new instance of . /// /// Flag to buffer entire request body before deserializing it. [Obsolete("This constructor is obsolete and will be removed in a future version.")] public XmlSerializerInputFormatter(bool suppressInputFormatterBuffering) : this() { _suppressInputFormatterBuffering = suppressInputFormatterBuffering; } /// /// Initializes a new instance of . /// /// The . public XmlSerializerInputFormatter(MvcOptions options) #pragma warning disable CS0618 : this() #pragma warning restore CS0618 { _options = options; } /// /// Gets the list of to /// provide the wrapping type for de-serialization. /// public IList WrapperProviderFactories { get; } /// /// Indicates the acceptable input XML depth. /// public int MaxDepth { get { return _readerQuotas.MaxDepth; } set { _readerQuotas.MaxDepth = value; } } /// /// The quotas include - DefaultMaxDepth, DefaultMaxStringContentLength, DefaultMaxArrayLength, /// DefaultMaxBytesPerRead, DefaultMaxNameTableCharCount /// public XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas => _readerQuotas; /// public virtual InputFormatterExceptionPolicy ExceptionPolicy { get { if (GetType() == typeof(XmlSerializerInputFormatter)) { return InputFormatterExceptionPolicy.MalformedInputExceptions; } return InputFormatterExceptionPolicy.AllExceptions; } } /// public override async Task ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var request = context.HttpContext.Request; var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering; if (!request.Body.CanSeek && !suppressInputFormatterBuffering) { // XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously // read everything into a buffer, and then seek back to the beginning. request.EnableBuffering(); Debug.Assert(request.Body.CanSeek); await request.Body.DrainAsync(CancellationToken.None); request.Body.Seek(0L, SeekOrigin.Begin); } try { using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding)) { var type = GetSerializableType(context.ModelType); var serializer = GetCachedSerializer(type); var deserializedObject = serializer.Deserialize(xmlReader); // Unwrap only if the original type was wrapped. if (type != context.ModelType) { if (deserializedObject is IUnwrappable unwrappable) { deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType); } } return InputFormatterResult.Success(deserializedObject); } } // XmlSerializer wraps actual exceptions (like FormatException or XmlException) into an InvalidOperationException // https://github.com/dotnet/corefx/blob/master/src/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs#L652 catch (InvalidOperationException exception) when (exception.InnerException is FormatException || exception.InnerException is XmlException) { throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception); } } /// protected override bool CanReadType(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } return GetCachedSerializer(GetSerializableType(type)) != null; } /// /// Gets the type to which the XML will be deserialized. /// /// The declared type. /// The type to which the XML will be deserialized. protected virtual Type GetSerializableType(Type declaredType) { if (declaredType == null) { throw new ArgumentNullException(nameof(declaredType)); } var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( new WrapperProviderContext(declaredType, isSerialization: false)); return wrapperProvider?.WrappingType ?? declaredType; } /// /// 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(Stream readStream, Encoding encoding) { if (readStream == null) { throw new ArgumentNullException(nameof(readStream)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } return XmlDictionaryReader.CreateTextReader(readStream, encoding, _readerQuotas, onClose: null); } /// /// Called during deserialization to get the . /// /// The used during deserialization. protected virtual XmlSerializer CreateSerializer(Type type) { try { // If the serializer does not support this type it will throw an exception. return new XmlSerializer(type); } catch (Exception) { // We do not surface the caught exception because if CanRead returns // false, then this Formatter is not picked up at all. return null; } } /// /// Gets the cached serializer or creates and caches the serializer for the given type. /// /// The instance. protected virtual XmlSerializer GetCachedSerializer(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (!_serializerCache.TryGetValue(type, out var serializer)) { serializer = CreateSerializer(type); if (serializer != null) { _serializerCache.TryAdd(type, serializer); } } return (XmlSerializer)serializer; } } }