/// <inheritdoc /> public async Task <Stream> ConvertAsync(Stream stream, CancellationToken cancellationToken = default) { EnsureArg.IsNotNull(stream, nameof(stream)); int bufferThreshold = DefaultBufferThreshold; long? bufferLimit = null; Stream seekableStream = null; if (!stream.CanSeek) { try { seekableStream = new FileBufferingReadStream(stream, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory); _httpContextAccessor.HttpContext?.Response.RegisterForDisposeAsync(seekableStream); await seekableStream.DrainAsync(cancellationToken); } catch (InvalidDataException) { // This will result in bad request, we need to handle this differently when we make the processing serial. throw new DicomFileLengthLimitExceededException(_storeConfiguration.Value.MaxAllowedDicomFileSize); } catch (IOException ex) { throw new InvalidMultipartBodyPartException(ex); } } else { seekableStream = stream; } seekableStream.Seek(0, SeekOrigin.Begin); return(seekableStream); }
/// <summary> /// Resolve the <paramref name="stream"/> for the <see cref="FileUploadProvider"/> and awaits the upload. /// </summary> /// <param name="stream">The <see cref="Stream"/> containing uploaded data.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param> /// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="null"/>, <see cref="ErrorMessageResponse"/> otherwise.</returns> public async Task <ErrorMessageResponse> Completion(Stream stream, CancellationToken cancellationToken) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (ticketExpiryCts.IsCancellationRequested) { return(new ErrorMessageResponse(ErrorCode.ResourceNotPresent)); } Stream bufferedStream = null; if (requireSynchronousIO) { // big reads, we should buffer to disk bufferedStream = new FileBufferingReadStream(stream, DefaultIOManager.DefaultBufferSize); await bufferedStream.DrainAsync(cancellationToken).ConfigureAwait(false); } using (bufferedStream) { taskCompletionSource.TrySetResult(bufferedStream ?? stream); await completionTcs.Task.WithToken(cancellationToken).ConfigureAwait(false); return(errorMessage); } }
private static async ValueTask <byte[]> ReadDataAsync(JsonTranscodingServerCallContext serverCallContext) { // Buffer to disk if content is larger than 30Kb. // Based on value in XmlSerializer and NewtonsoftJson input formatters. const int DefaultMemoryThreshold = 1024 * 30; var memoryThreshold = DefaultMemoryThreshold; var contentLength = serverCallContext.HttpContext.Request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } using var fs = new FileBufferingReadStream(serverCallContext.HttpContext.Request.Body, memoryThreshold); // Read the request body into buffer. // No explicit cancellation token. Request body uses underlying request aborted token. await fs.DrainAsync(CancellationToken.None); fs.Seek(0, SeekOrigin.Begin); var data = new byte[fs.Length]; var read = fs.Read(data); Debug.Assert(read == data.Length); return(data); }
/// <inheritdoc /> public override async ValueTask <T> DeserializeFromRequest <T>(RequestExecutionContext context) { var fileReader = new FileBufferingReadStream(context.HttpContext.Request.Body, _memoryThreshold); context.HttpContext.Response.RegisterForDispose(fileReader); try { await fileReader.DrainAsync(context.HttpContext.RequestAborted); fileReader.Seek(0L, SeekOrigin.Begin); using var textReader = new StreamReader(fileReader); using var xmlReader = new XmlTextReader(textReader); var serializer = GetSerializer(typeof(T)); return((T)serializer.Deserialize(xmlReader)); } finally { await fileReader.DisposeAsync(); } }
private async Task <InputFormatterResult> BufferedStreamAsync(InputFormatterContext context) { using var readStream = new FileBufferingReadStream(context.HttpContext.Request.Body, _memoryBufferThreshold); await readStream.DrainAsync(CancellationToken.None); readStream.Position = 0; var payload = _model.Deserialize(readStream, value: null, type: context.ModelType); return(InputFormatterResult.Success(payload)); }
/// <inheritdoc /> public async Task <Stream> ConvertAsync(Stream stream, CancellationToken cancellationToken = default) { EnsureArg.IsNotNull(stream, nameof(stream)); int bufferThreshold = DefaultBufferThreshold; long? bufferLimit = null; Stream seekableStream = null; if (!stream.CanSeek) { seekableStream = new FileBufferingReadStream(stream, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory); _httpContextAccessor.HttpContext?.Response.RegisterForDisposeAsync(seekableStream); await seekableStream.DrainAsync(cancellationToken); } else { seekableStream = stream; } seekableStream.Seek(0, SeekOrigin.Begin); return(seekableStream); }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var request = httpContext.Request; var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering; var readStream = request.Body; var disposeReadStream = false; if (readStream.CanSeek) { // The most common way of getting here is the user has request buffering on. // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous // reads as part of the deserialization. // To avoid this, drain and reset the stream. var position = request.Body.Position; await readStream.DrainAsync(CancellationToken.None); readStream.Position = position; } else if (!suppressInputFormatterBuffering) { // JSON.Net 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. var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold; var contentLength = request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); // Ensure the file buffer stream is always disposed at the end of a request. httpContext.Response.RegisterForDispose(readStream); await readStream.DrainAsync(CancellationToken.None); readStream.Seek(0L, SeekOrigin.Begin); disposeReadStream = true; } var successful = true; Exception?exception = null; object? model; using (var streamReader = context.ReaderFactory(readStream, encoding)) { using var jsonReader = new JsonTextReader(streamReader); jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(context); jsonSerializer.Error += ErrorHandler; if (_jsonOptions.ReadJsonWithRequestCulture) { jsonSerializer.Culture = CultureInfo.CurrentCulture; } try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); if (disposeReadStream) { await readStream.DisposeAsync(); } } } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (exception is not null && exception is not(JsonException or OverflowException or FormatException)) { // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary. // We only need to rethrow an exception if we believe it needs to be handled by something further up // the stack. // JsonException, OverflowException, and FormatException are assumed to be only encountered when // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else // needs to be rethrown. var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); void ErrorHandler(object?sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { // Skipping error, if it's already marked as handled // This allows user code to implement its own error handling if (eventArgs.ErrorContext.Handled) { return; } successful = false; // The following addMember logic is intended to append the names of missing required properties to the // ModelStateDictionary key. Normally, just the ModelName and ErrorContext.Path is used for this key, // but ErrorContext.Path does not include the missing required property name like we want it to. // For example, given the following class and input missing the required "Name" property: // // class Person // { // [JsonProperty(Required = Required.Always)] // public string Name { get; set; } // } // // We will see the following ErrorContext: // // Error {"Required property 'Name' not found in JSON. Path 'Person'..."} System.Exception {Newtonsoft.Json.JsonSerializationException} // Member "Name" object {string} // Path "Person" string // // So we update the path used for the ModelStateDictionary key to be "Person.Name" instead of just "Person". // See https://github.com/aspnet/Mvc/issues/8509 var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member as string; // There are some deserialization exceptions that include the member in the path but not at the end. // For example, given the following classes and invalid input like { "b": { "c": { "d": abc } } }: // // class A // { // public B B { get; set; } // } // class B // { // public C C { get; set; } // } // class C // { // public string D { get; set; } // } // // We will see the following ErrorContext: // // Error {"Unexpected character encountered while parsing value: b. Path 'b.c.d'..."} System.Exception {Newtonsoft.Json.JsonReaderException} // Member "c" object {string} // Path "b.c.d" string // // Notice that Member "c" is in the middle of the Path "b.c.d". The error handler gets invoked for each level of nesting. // null, "b", "c" and "d" are each a Member in different ErrorContexts all reporting the same parsing error. // // The parsing error is reported as a JsonReaderException instead of as a JsonSerializationException like // for missing required properties. We use the exception type to filter out these errors and keep the path used // for the ModelStateDictionary key as "b.c.d" instead of "b.c.d.c" // See https://github.com/dotnet/aspnetcore/issues/33451 var addMember = !string.IsNullOrEmpty(member) && eventArgs.ErrorContext.Error is JsonSerializationException; // There are still JsonSerilizationExceptions that set ErrorContext.Member but include it at the // end of ErrorContext.Path already. The following logic attempts to filter these out. if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member !.Length) { // Add Member in Path.Member case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends or starts with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith($".{member}", StringComparison.Ordinal) && !path.EndsWith($"['{member}']", StringComparison.Ordinal) && !path.EndsWith($"[{member}]", StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); _logger.JsonInputException(exception); // Error must always be marked as handled // Failure to do so can cause the exception to be rethrown at every recursive level and // overflow the stack for x64 CLR processes eventArgs.ErrorContext.Handled = true; }
/// <inheritdoc /> public override async Task <InputFormatterResult> 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; var readStream = request.Body; var disposeReadStream = false; if (!request.Body.CanSeek && !suppressInputFormatterBuffering) { // JSON.Net 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. var memoryThreshold = DefaultMemoryThreshold; var contentLength = request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); await readStream.DrainAsync(CancellationToken.None); readStream.Seek(0L, SeekOrigin.Begin); disposeReadStream = true; } var successful = true; Exception exception = null; object model; using (var streamReader = context.ReaderFactory(readStream, encoding)) { using var jsonReader = new JsonTextReader(streamReader); jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(context); jsonSerializer.Error += ErrorHandler; try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); if (disposeReadStream) { await readStream.DisposeAsync(); } } } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (!(exception is JsonException || exception is OverflowException)) { var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { successful = false; // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path. var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member?.ToString(); var addMember = !string.IsNullOrEmpty(member); if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member.Length) { // Add Member in Path.Memb case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith("." + member, StringComparison.Ordinal) && !path.EndsWith("['" + member + "']", StringComparison.Ordinal) && !path.EndsWith("[" + member + "]", StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); _logger.JsonInputException(exception); // Error must always be marked as handled // Failure to do so can cause the exception to be rethrown at every recursive level and // overflow the stack for x64 CLR processes eventArgs.ErrorContext.Handled = true; } }
/// <inheritdoc /> public override async Task <InputFormatterResult> 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; Stream readStream = new NonDisposableStream(request.Body); if (!request.Body.CanSeek && !_options.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. var memoryThreshold = DefaultMemoryThreshold; var contentLength = request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); await readStream.DrainAsync(CancellationToken.None); readStream.Seek(0L, SeekOrigin.Begin); } try { using var xmlReader = CreateXmlReader(readStream, 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 != null && exception.InnerException.InnerException == null && string.Equals("Microsoft.GeneratedCode", exception.InnerException.Source, StringComparison.InvariantCulture)) { // Know this was an XML parsing error because the inner Exception was thrown in the (generated) // assembly the XmlSerializer uses for parsing. The problem did not arise lower in the stack i.e. it's // not (for example) an out-of-memory condition. throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception.InnerException); } catch (InvalidOperationException exception) when(exception.InnerException is FormatException || exception.InnerException is XmlException) { throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception.InnerException); } finally { if (readStream is FileBufferingReadStream fileBufferingReadStream) { await fileBufferingReadStream.DisposeAsync(); } } }
/// <inheritdoc /> public override async Task <InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (encoding == null) { throw new ArgumentNullException(nameof(encoding)); } var httpContext = context.HttpContext; var request = httpContext.Request; var suppressInputFormatterBuffering = _options.SuppressInputFormatterBuffering; var readStream = request.Body; var disposeReadStream = false; if (readStream.CanSeek) { // The most common way of getting here is the user has request buffering on. // However, request buffering isn't eager, and consequently it will peform pass-thru synchronous // reads as part of the deserialization. // To avoid this, drain and reset the stream. var position = request.Body.Position; await readStream.DrainAsync(CancellationToken.None); readStream.Position = position; } else if (!suppressInputFormatterBuffering) { // JSON.Net 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. var memoryThreshold = _jsonOptions.InputFormatterMemoryBufferThreshold; var contentLength = request.ContentLength.GetValueOrDefault(); if (contentLength > 0 && contentLength < memoryThreshold) { // If the Content-Length is known and is smaller than the default buffer size, use it. memoryThreshold = (int)contentLength; } readStream = new FileBufferingReadStream(request.Body, memoryThreshold); // Ensure the file buffer stream is always disposed at the end of a request. httpContext.Response.RegisterForDispose(readStream); await readStream.DrainAsync(CancellationToken.None); readStream.Seek(0L, SeekOrigin.Begin); disposeReadStream = true; } var successful = true; Exception?exception = null; object? model; using (var streamReader = context.ReaderFactory(readStream, encoding)) { using var jsonReader = new JsonTextReader(streamReader); jsonReader.ArrayPool = _charPool; jsonReader.CloseInput = false; var type = context.ModelType; var jsonSerializer = CreateJsonSerializer(context); jsonSerializer.Error += ErrorHandler; if (_jsonOptions.ReadJsonWithRequestCulture) { jsonSerializer.Culture = CultureInfo.CurrentCulture; } try { model = jsonSerializer.Deserialize(jsonReader, type); } finally { // Clean up the error handler since CreateJsonSerializer() pools instances. jsonSerializer.Error -= ErrorHandler; ReleaseJsonSerializer(jsonSerializer); if (disposeReadStream) { await readStream.DisposeAsync(); } } } if (successful) { if (model == null && !context.TreatEmptyInputAsDefaultValue) { // Some nonempty inputs might deserialize as null, for example whitespace, // or the JSON-encoded value "null". The upstream BodyModelBinder needs to // be notified that we don't regard this as a real input so it can register // a model binding error. return(InputFormatterResult.NoValue()); } else { return(InputFormatterResult.Success(model)); } } if (exception is not null && exception is not(JsonException or OverflowException or FormatException)) { // At this point we've already recorded all exceptions as an entry in the ModelStateDictionary. // We only need to rethrow an exception if we believe it needs to be handled by something further up // the stack. // JsonException, OverflowException, and FormatException are assumed to be only encountered when // parsing the JSON and are consequently "safe" to be exposed as part of ModelState. Everything else // needs to be rethrown. var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); exceptionDispatchInfo.Throw(); } return(InputFormatterResult.Failure()); void ErrorHandler(object?sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) { // Skipping error, if it's already marked as handled // This allows user code to implement its own error handling if (eventArgs.ErrorContext.Handled) { return; } successful = false; // When ErrorContext.Path does not include ErrorContext.Member, add Member to form full path. var path = eventArgs.ErrorContext.Path; var member = eventArgs.ErrorContext.Member?.ToString(); var addMember = !string.IsNullOrEmpty(member); if (addMember) { // Path.Member case (path.Length < member.Length) needs no further checks. if (path.Length == member !.Length) { // Add Member in Path.Memb case but not for Path.Path. addMember = !string.Equals(path, member, StringComparison.Ordinal); } else if (path.Length > member.Length) { // Finally, check whether Path already ends with Member. if (member[0] == '[') { addMember = !path.EndsWith(member, StringComparison.Ordinal); } else { addMember = !path.EndsWith("." + member, StringComparison.Ordinal) && !path.EndsWith("['" + member + "']", StringComparison.Ordinal) && !path.EndsWith("[" + member + "]", StringComparison.Ordinal); } } } if (addMember) { path = ModelNames.CreatePropertyModelName(path, member); } // Handle path combinations such as ""+"Property", "Parent"+"Property", or "Parent"+"[12]". var key = ModelNames.CreatePropertyModelName(context.ModelName, path); exception = eventArgs.ErrorContext.Error; var metadata = GetPathMetadata(context.Metadata, path); var modelStateException = WrapExceptionForModelState(exception); context.ModelState.TryAddModelError(key, modelStateException, metadata); _logger.JsonInputException(exception); // Error must always be marked as handled // Failure to do so can cause the exception to be rethrown at every recursive level and // overflow the stack for x64 CLR processes eventArgs.ErrorContext.Handled = true; }